Mockito:嘲弄“Blackbox”依赖

时间:2011-12-22 19:30:36

标签: java unit-testing mocking mockito

所以我被要求为我们的开发团队阅读模拟和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不应该做(或返回)任何事情。

我已检查并重新检查:mockSubscriberinit,不是publicstatic,而是返回final。根据文档,Mockito应该没有问题嘲笑这个对象。

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

adaptor

但是因为我不能添加任何这样的二传手(请阅读我上面的注释),我不知道如何强迫这样的关联。所以,有几个非常密切相关的问题:

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

提前致谢!

5 个答案:

答案 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。