Mockito:嘲弄“Blackbox”依赖

所以我被要求为我们的开发团队阅读模拟和BDD,并玩嘲笑,以便改进我们现有的一些unit testing(作为实验)。

我最终选择与Mockito一起出于多种原因(有些原因超出我的控制范围),但是因为它支持对模拟不适合的实例进行存根和模拟。

我整天都在学习Mockito,嘲笑(一般)和BDD。 现在我准备深入挖掘并开始增强我们的unit testing。

所以我们有一个名为WebAdaptor的类,它有一个run()方法:

 public class WebAdaptor { private Subscriber subscriber; public void run() { subscriber = new Subscriber(); subscriber.init(); } } 

请注意:我没有办法修改此代码(出于此问题范围之外的原因!)。 因此,我无法Subscriber添加setter方法,因此可以将其视为WebAdaptor内部无法访问的“黑WebAdaptor

我想编写一个包含Mockito模拟的unit testing,并使用该模拟来verify执行WebAdaptor::run()会导致调用Subscriber::init()

所以这就是我到目前为止(在WebAdaptorUnitTest ):

 @Test public void runShouldInvokeSubscriberInit() { // Given Subscriber mockSubscriber = mock(Subscriber.class); WebAdaptor adaptor = new WebAdaptor(); // When adaptor.run(); // Then verify(mockSubscriber).init(); } 

当我运行这个测试时,实际的Subscriber::init()方法被执行(我可以从控制台输出中看到并看到在我的本地系统上生成的文件), 而不是 mockSubscriber ,它不应该(或返回)任何东西。

我检查并重新检查: initpublic ,既不是static也不是final ,它返回void 。 根据文档,Mockito应该没有问题嘲笑这个对象。

所以它让我思考:我是否需要明确地将mockSubscriberadaptor相关联? 如果是这种情况,那么通常情况下,以下情况通常会解决它:

 adaptor.setSubscriber(mockSubscriber); 

但由于我不能添加任何这样的定位器(请阅读我上面的注释),我不知道如何强制这样的关联。 所以,有几个非常密切相关的问题:

  • 任何人都可以确认我已正确设置测试(使用Mockito API)吗?
  • 我怀疑失踪的二传手是否正确? (我是否需要通过setter关联这些对象?)
  • 如果我的上述怀疑是真的,我无法修改WebAdaptor ,我的处置是否有任何规避?

提前致谢!

您需要将模拟注入您正在测试的类中。 您无需访问Subscriber。 mockito和其他模拟框架的帮助方式是您不需要访问与之交互的对象。 但是,您需要一种方法将模拟对象放入您正在测试的类中。

 public class WebAdaptor { public WebAdaptor(Subscriber subscriber) { /* Added a new constructor */ this.subscriber = subscriber; } private Subscriber subscriber; public void run() { subscriber.init(); } } 

现在,您可以在模拟上validation您的交互,而不是在真实对象上。

 @Test public void runShouldInvokeSubscriberInit() { // Given Subscriber mockSubscriber = mock(Subscriber.class); WebAdaptor adaptor = new WebAdaptor(mockSubscriber); // Use the new constructor // When adaptor.run(); // Then verify(mockSubscriber).init(); } 

如果将订阅服务器添加到构造函数不是正确的方法,您还可以考虑使用工厂允许WebAdaptor从您控制的工厂实例化新的订阅服务器对象。 然后,您可以模拟工厂提供模拟订阅服务器。

如果您不想更改生产代码并且仍然能够模拟Subscriber类的function,那么您应该查看PowerMock。 它与Mockito一起工作正常,并允许您模拟新对象的创建。

 Subscriber mockSubscriber = mock(Subscriber.class); whenNew(Subscriber.class).withNoArguments().thenReturn(mockSubscriber); 

有关详细信息,请参阅PowerMock框架的文档 。

有一种方法可以将模拟注入到测试的类中,而无需对代码进行任何修改。 这可以使用Mockito WhiteBox完成 。 这是一个非常好的function,可用于从测试中注入您的Class Under Test的依赖项。 以下是一个关于它如何工作的简单示例,

 @Mock Subscriber mockSubscriber; WebAdaptor cut = new WebAdaptor(); @Before public void setup(){ //sets the internal state of the field in the class under test even if it is private MockitoAnnotations.initMocks(this); //Now the whitebox functionality injects the dependent object - mockSubscriber //into the object which depends on it - cut Whitebox.setInternalState(cut, "subscriber", mockSubscriber); } @Test public void runShouldInvokeSubscriberInit() { cut.run(); verify(mockSubscriber).init(); } 

希望这可以帮助 :-)

您可以使用PowerMock模拟构造函数调用而不更改原始代码:

 import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(WebAdaptor.class) public class WebAdaptorTest { @Test public void testRunCallsSubscriberInit() { final Subscriber subscriber = mock(Subscriber.class); whenNew(Subscriber.class).withNoArguments().thenReturn(subscriber); new WebAdaptor().run(); verify(subscriber).init(); } } 

您无法在当前实现中使用Mockito模拟订阅服务器。

您遇到的问题是构建订阅服务器然后立即访问,Mockito无法在创建后但在调用init方法之前替换(或间谍)订阅服务器实例。

 public void run() { subscriber = new Subscriber(); // Mockito would need to jump in here subscriber.init(); } 

David V的答案通过将Subscriber添加到构造函数来解决这个问题。 保留隐藏订阅服务器构造的替代方法是在WebAdapter no-arg构造函数中实例化订阅服务器,然后在调用run方法之前使用reflection替换该实例。

您的WebAdapter看起来像这样,

 public class WebAdaptor { private Subscriber subscriber; public WebAdaptor() { subscriber = new Subscriber(); } public void run() { subscriber.init(); } } 

您可以使用Springframework测试模块中的ReflectionTestUtils将依赖项注入该私有字段。

 @Test public void runShouldInvokeSubscriberInit() { // Given Subscriber mockSubscriber = mock(Subscriber.class); WebAdaptor adaptor = new WebAdaptor(); ReflectionTestUtils.setField( adaptor "subscriber", mockSubscriber ); // When adaptor.run(); // This will call mockSubscriber.init() // Then verify(mockSubscriber).init(); } 

ReflectionTestUtils实际上只是Javareflection的包装器,如果没有Spring依赖,也可以手动实现(更加冗长)。

Mockito的WhiteBox (正如Bala建议的那样)可以在这里代替ReflectionTestUtils ,它包含在Mockito的内部包中,所以我回避它,YMMV。