Mockito:模拟“黑盒";依赖项

2022-01-14 00:00:00 mocking unit-testing mockito java

因此,我被要求为我们的开发团队阅读有关 mocking 和 BDD 的内容,并使用 mocks 来改进我们现有的一些单元测试(作为实验).

So I have been asked to read up on mocking and BDD for our development team and play around with mocks so as to improve a handful of our existing unit tests (as an experiment).

我最终选择使用 Mockito 的原因有很多(有些原因超出了我的控制范围),但就是因为它同时支持存根和模拟,以便在不适合模拟的情况下使用.

I have ultimately chosen to go with Mockito for a number of reasons (some outside the scope of my control), but namely because it supports both stubbing and mocking for instances when mocking would not be appropriate.

我整天都在学习 Mockito、mock(一般)和 BDD.现在我已准备好深入研究并开始扩充我们的单元测试.

I have spent all day learning about Mockito, mocking (in general) and BDD. And now I am ready to dig in and start augmenting our unit tests.

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

So we have a class called WebAdaptor that has a run() method:

public class WebAdaptor {

    private Subscriber subscriber;

    public void run() {

        subscriber = new Subscriber();
        subscriber.init();
    }
}

请注意:我无法修改此代码(出于此问题范围之外的原因!).因此我确实不有能力为Subscriber添加一个setter方法,因此它可以被认为是我的WebAdaptor.

Please note: I do not have a way to modify this code (for reasons outside the scope of this question!). Thus I do not have the ability to add a setter method for Subscriber, and thus it can be thought of as an unreachable "blackbox" inside of my WebAdaptor.

我想编写一个包含 Mockito 模拟的单元测试,并使用该模拟来 verify 执行 WebAdaptor::run() 导致 Subscriber::init() 被调用.

I want to write a unit test which incorporates a Mockito mock, and uses that mock to verify that executing WebAdaptor::run() causes Subscriber::init() to be called.

这就是我目前所得到的(在 WebAdaptorUnitTest 内):

So here's what I've got so far (inside 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,它不应该做(或返回)任何事情.

When I run this test, the actual Subscriber::init() method gets executed (I can tell from the console output and seeing files being generated on my local system), not the mockSubscriber, which shouldn't do (or return) anything.

我已经检查并重新检查:initpublic,既不是 static 也不是 final,并且它返回 void.根据文档,Mockito 模拟这个对象应该没有问题.

I have checked and re-checked: init is public, is neither static or final, and it returns void. According to the docs, Mockito should have no problem mocking this object.

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

So it got me thinking: do I need to explictly associate the mockSubscriber with the adaptor? If this is a case, then ordinarily, the following would normally fix it:

adaptor.setSubscriber(mockSubscriber);

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

But since I cannot add any such setter (please read my note above), I'm at a loss as to how I could force such an association. So, several very-closely-related questions:

  • 谁能确认我已正确设置测试(使用 Mockito API)?
  • 我对失踪二传手的怀疑是否正确?(我需要通过 setter 关联这些对象吗?)
  • 如果我的上述怀疑属实,并且我无法修改 WebAdaptor,是否有任何规避措施可供我使用?
  • Can anyone confirm that I've set the test up correctly (using the Mockito API)?
  • Is my suspicion about the missing setter correct? (Do I need to associate these objects via a setter?)
  • If my above suspicion is true, and I can't modify WebAdaptor, are there any circumventions at my dispose?

提前致谢!

推荐答案

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

You need to inject the mock into the class which you are testing. You do not need access to Subscriber. The way mockito and other mocking frameworks help is that you do not need access to objects which you are interacting with. You do however need a way to get mock objects into the class you are testing.

public class WebAdaptor {

    public WebAdaptor(Subscriber subscriber) { /* Added a new constructor */
       this.subscriber = subscriber;
    }

    private Subscriber subscriber;

    public void run() {
        subscriber.init();
    }
}

现在您可以在模拟对象上验证您的交互,而不是在真实对象上验证.

Now you can verify your interactions on the mock, rather than on the real object.

@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 从您控制的工厂实例化新订阅者对象.然后,您可以模拟工厂以提供模拟订阅者.

If adding the Subscriber to the constructor is not the correct approach, you could also consider using a factory to allow WebAdaptor to instantiate new Subscriber objects from a factory which you control. You could then mock the factory to provider mock Subscribers.

相关文章