所以我被要求为我们的开发团队阅读模拟和BDD,并玩嘲笑,以便改进我们现有的一些单元测试(作为实验)。
我最终选择和Mockito一起出于多种原因(有些原因超出了我的控制范围),但是因为它在模拟不合适的情况下支持实例的存根和模拟。
我花了一整天时间学习Mockito,嘲笑(一般)和BDD。现在我准备深入挖掘并开始增强我们的单元测试。
所以我们有一个名为WebAdaptor
的类,它有一个run()
方法:
public class WebAdaptor {
private Subscriber subscriber;
public void run() {
subscriber = new Subscriber();
subscriber.init();
}
}
请注意:我无法修改此代码(出于此问题范围之外的原因!)。因此,我不能够为Subscriber
添加setter方法,因此可以将其视为我WebAdaptor
内部无法访问的“黑盒子”。
我想编写一个包含Mockito
模拟的单元测试,并将该模拟用于执行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()
方法被执行(我可以从控制台输出中看出并看到在我的本地系统上生成的文件),不 {{1不应该做(或返回)任何事情。
我已检查并重新检查:mockSubscriber
为init
,不是public
或static
,而是返回final
。根据文档,Mockito应该没有问题嘲笑这个对象。
所以它让我思考:我是否需要明确地将void
与mockSubscriber
相关联?如果是这种情况,那么通常情况下,以下情况通常会解决它:
adaptor
但是因为我不能添加任何这样的二传手(请阅读我上面的注释),我不知道如何强迫这样的关联。所以,有几个非常密切相关的问题:
adaptor.setSubscriber(mockSubscriber);
,我的处置是否有任何规避?提前致谢!
答案 0 :(得分:10)
您需要将模拟注入您正在测试的类中。您无需访问Subscriber。 mockito和其他模拟框架的帮助方式是您不需要访问与之交互的对象。但是,您需要一种方法将模拟对象放入您正在测试的类中。
public class WebAdaptor {
public WebAdaptor(Subscriber subscriber) { /* Added a new constructor */
this.subscriber = subscriber;
}
private Subscriber subscriber;
public void run() {
subscriber.init();
}
}
现在,您可以在模拟上验证您的交互,而不是在真实对象上验证。
@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从您控制的工厂实例化新的订阅服务器对象。然后,您可以模拟工厂提供模拟订阅者。
答案 1 :(得分:5)
如果您不想更改生产代码并且仍然能够模拟Subscriber类的功能,那么您应该查看PowerMock。它与Mockito一起工作正常,并允许您模拟新对象的创建。
Subscriber mockSubscriber = mock(Subscriber.class);
whenNew(Subscriber.class).withNoArguments().thenReturn(mockSubscriber);
documentation for the PowerMock框架中解释了更多细节。
答案 2 :(得分:2)
有一种方法可以将模拟注入到被测试的类中,而无需对代码进行任何修改。这可以使用Mockito WhiteBox来完成。这是一个非常好的功能,可用于从测试中注入您的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();
}
希望这会有所帮助: - )
答案 3 :(得分:2)
您可以使用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();
}
}
答案 4 :(得分:1)
您无法在当前的实施中使用Mockito模拟订阅者。
您遇到的问题是构建订阅服务器然后立即访问,Mockito无法在创建后但在调用init方法之前替换(或窥探)订阅服务器实例。
public void run() {
subscriber = new Subscriber();
// Mockito would need to jump in here
subscriber.init();
}
David V的回答通过将订阅者添加到构造函数来解决这个问题。保留隐藏订阅服务器构造的替代方法是在WebAdapter no-arg构造函数中实例化订阅服务器,然后在调用run方法之前使用反射来替换该实例。
您的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
实际上只是关于Java反射的包装器,如果没有Spring依赖,也可以手动实现(更加冗长)。
Mockito的WhiteBox
(正如Bala建议的那样)可以代替ReflectionTestUtils
在这里工作,它包含在Mockito的内部包中,所以我回避它,YMMV。