简体   繁体   English

打破局部依赖关系以单元测试void方法

[英]Breaking a local dependency to unit test a void method

I am practicing with mockito, but I am a bit stuck on how to test a method that depends on a call to method in a local object. 我正在使用mockito进行练习,但我对如何测试依赖于本地对象中方法调用的方法有点困惑。 See the following example: 请参阅以下示例:

public class Worker {          

    public void work() {
                   Vodka vodka = new Vodka();
                   vodka.drink();
     }
}

This worker, instead of doing his job, he likes drinking. 这个工人不喜欢做自己的工作,而是喜欢喝酒。 But I want to add a test to prove that he drinks while he works. 但是我想补充一个测试来证明他在工作时喝酒。 But there is no way of doing so, because I must verify that the method drink() is called when the method work is called. 但是没有办法这样做,因为我必须验证调用方法工作时调用方法drink()。 I think you agree with me, that this is impossible to test, so I need to break the dependency before starting to test. 我认为你同意我的意见,这是不可能测试的,所以我需要在开始测试之前打破依赖。 Here is my first doubt, what do you think is the best way of breaking such dependency? 这是我的第一个疑问,你认为打破这种依赖的最佳方法是什么? If I just change the scope of the vodka object to global, I think would not be good(I don't want to expose it to other parts of the class). 如果我只是将伏特加对象的范围更改为全局,我认为不会很好(我不想将它暴露给类的其他部分)。 I thought about creating a factory, something like this: 我想过创建一个像这样的工厂:

public class Worker {          

    private VodkaFactory vodkaFactory = new VodkaFactory();


    public void work() {
                   Vodka vodka = vodkaFactory.getVodka();
                   vodka.drink();
     }
}

I am not sure if I did break the dependency correctly, but what I want to do now, is test that the method drink() is called when work() is executed. 我不确定我是否确实打破了依赖关系,但我现在要做的是测试在执行work()时调用方法drink()。 I tried this with no luck: 我试了这个没有运气:

@Test
    public void
    does_the_worker_drink_while_working
    () {
        VodkaFactory vodkaFactory = mock(VodkaFactory.class);
        Vodka vodka = mock(Vodka.class);
        Worker worker = new Worker();
        worker.work();
        when(vodkaFactory.getVodka()).thenReturn(vodka);
        verify(vodka,times(1)).drink();
    }

I mock the factory and the when will detect that a new Vodka object is created by the factory. 我嘲笑工厂,何时会发现工厂创建了一个新的伏特加对象。 But then when I wan to verify that that method calls 1 time the method drink(), mockito tells me: 但是当我想验证该方法调用方法drink()时,我会告诉我:

Wanted but not invoked:
vodka.drink();
-> at testing_void_methods_from_local_objects.WorkerSpecification.does_the_worker_drink_while_working(WorkerSpecification.java:22)
Actually, there were zero interactions with this mock.

I am not stubbing correctly or I am doing something wrong. 我没有正确存根或者我做错了什么。 Could you give me a hand completing this test and also clarify me what would be the best way of testing such untesteable methods? 你能帮我完成一下这个测试,并告诉我测试这些无法测试的方法的最佳方法是什么?

I know mockito has a method called, doAnswer() which is used to mock a method call,do you think it can be useful in this case? 我知道mockito有一个名为doAnswer()的方法,它用于模拟方法调用,你觉得它在这种情况下有用吗? How should I use it? 我该怎么用?

UPDATE: 更新:

I am following the suggestions to get the when() called before the work() and also I am trying to allow the factory to be set from outside of the class: 我遵循建议在work()之前调用when() ,并且我也试图允许从类外部设置工厂:

@Test
public void
does_the_worker_drink_while_working
() {
    VodkaFactory vodkaFactory = mock(VodkaFactory.class);
    Vodka vodka = mock(Vodka.class);
    Worker worker = new Worker();
    when(vodkaFactory.getVodka()).thenReturn(vodka);
    worker.work();
    verify(vodka,times(1)).drink();
}

This is now the production code now: 现在这是生产代码:

public class Worker {          


        private VodkaFactory vodkaFactory;

        public void work() {
                       Vodka vodka = vodkaFactory.getVodka();
                       vodka.drink();
         }

         public void setVodkaFactory(VodkaFactory vodkaFactory) {
               this.vodkaFactory = vodkaFactory;
         }

The exception that I get is the following: 我得到的例外情况如下:

 java.lang.NullPointerException
        at testing_void_methods_called_from_local_objects.Worker.work(Worker.java:9)

This is the line that says vodka.drink() 这是说vodka.drink()

Sorry by I still confused on what is the problem. 抱歉,我仍然对这个问题感到困惑。

Your worker creates his own factory class here: 您的工人在这里创建自己的工厂类:

private VodkaFactory vodkaFactory = new VodkaFactory();

The mock you are creating is completely detached from the worker instance and thus the lack of interaction. 您正在创建的模拟与工作器实例完全分离,因此缺乏交互。 To make it work, factory has to be injected to worker from "the outside", say via constructor injection . 为了使其工作,工厂必须从“外部”注入工人,比如通过构造注入

If this is legacy code, you could use reflection to replace private factory instance with mocked one. 如果这是遗留代码,则可以使用反射将私有工厂实例替换为模拟实例。

As noted by JB Nizet in comment, your mock setup comes after work is already called. 正如JB Nizet在评论中所指出的,你的模拟设置是在已经调用work之后发生的。 In order to make things right , inject mock and set it up before you call any code utilizing it. 为了使事情正确 ,请在调用任何使用它的代码之前注入模拟并进行设置。

You need to set your vodkaFactory: 你需要设置你的伏特加工厂:

@Test
public void
does_the_worker_drink_while_working() {
    VodkaFactory vodkaFactory = mock(VodkaFactory.class);
    Vodka vodka = mock(Vodka.class);
    Worker worker = new Worker();
    when(vodkaFactory.getVodka()).thenReturn(vodka);

    //call your setter        
    worker.setVodkaFactory(vodkaFactory);

    worker.work();
    verify(vodka,times(1)).drink();
}

There is a logical error in the code you are trying to test. 您尝试测试的代码中存在逻辑错误。 Because you have created VodkaFactory instance inside of the Worker class and moreover you have made that field private. 因为您已在Worker类中创建了VodkaFactory实例,而且您已将该字段VodkaFactory私有。

The best solution would be to pass a reference to VodkaFactory from outside of the class. 最好的解决方案是从类外部传递对VodkaFactory的引用。

public class Worker {          

    private VodkaFactory vodkaFactory;

    public void work() {
                   Vodka vodka = vodkaFactory.getVodka();
                   vodka.drink();
     }

     public void setVodkaFactory(VodkaFactory vf) {
           vodkaFactory = vf;
     }

}

Now, in your @Test you can pass your mocked VodkaFactory instance using setVodkaFactory setter. 现在,在@Test中,您可以使用setVodkaFactory setter传递VodkaFactory实例。

It is more comment than an answer. 这是一个评论而不是一个答案。 In addition to make factory an injectable dependency, you can also make sure to train your mock when(vodkaFactory.getVodka()).thenReturn(vodka); 除了使工厂成为可注射的依赖项之外,你还可以确保训练你的模拟when(vodkaFactory.getVodka()).thenReturn(vodka); before interacting with it worker.work(); 在与之交互之前worker.work();

The following is a complete JMockit unit test which exercises the Worker#work() method in isolation from the implementation of its Vodka dependency: 以下是一个完整的JMockit单元测试,它独立于Vodka依赖项的实现来运行Worker#work()方法:

@Test
public void workTest(@Mocked final Vodka mockBeverage)
{
    new Worker().work();

    new Verifications() {{ mockBeverage.drink(); times = 1; }};
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM