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



我正在练习mockito,但我有点卡在如何测试一个方法,依赖于调用方法在一个局部对象。请看下面的例子:

public class Worker {          
    public void work() {
                   Vodka vodka = new Vodka();
                   vodka.drink();
     }
}
这个工人不工作,他喜欢喝酒。但我想加一个测试来证明他工作时喝酒。但是没有办法这样做,因为我必须验证在调用方法work时是否调用了方法drink()。我想你同意我的观点,这是不可能测试的,所以我需要在开始测试之前打破依赖关系。这是我的第一个疑问,你认为打破这种依赖的最好方法是什么?如果我只是将vodka对象的作用域更改为global,我认为这并不好(我不想将它暴露给类的其他部分)。我想创建一个像这样的工厂:
public class Worker {          
    private VodkaFactory vodkaFactory = new VodkaFactory();

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

我不确定我是否正确地打破了依赖关系,但我现在要做的是测试当work()执行时是否调用方法drink()。我试了一下,没有运气:

@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();
    }

模拟工厂,when将检测到工厂创建了一个新的Vodka对象。但是,当我想验证该方法是否调用了1次方法drink()时,mockito告诉我:

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.

我没有正确地存根或我做错了什么。你能帮我完成这个测试吗?并告诉我测试这种不可测试的方法的最好方法是什么?

我知道mockito有一个名为doAnswer()的方法,用于模拟方法调用,你认为它在这种情况下有用吗?我应该如何使用它?

更新:

我正在遵循建议,在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();
}

现在是生产代码:

public class Worker {          

        private VodkaFactory vodkaFactory;
        public void work() {
                       Vodka vodka = vodkaFactory.getVodka();
                       vodka.drink();
         }
         public void setVodkaFactory(VodkaFactory vodkaFactory) {
               this.vodkaFactory = vodkaFactory;
         }

我得到的异常如下:

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

这一行写着vodka.drink()

对不起,我还是不明白是什么问题

您的worker在这里创建了自己的factory类:

private VodkaFactory vodkaFactory = new VodkaFactory();

您正在创建的mock与worker实例完全分离,因此缺乏交互。为了使它工作,factory必须从"外部"注入到worker中,比如通过构造函数注入。

如果这是遗留代码,您可以使用反射将私有工厂实例替换为模拟工厂实例。

正如JB Nizet在评论中所指出的,您的模拟设置是在work已经被调用之后进行的。为了使正确,在调用任何使用它的代码之前注入mock并设置它。

您需要设置您的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();
}

与其说是回答,不如说是评论。除了使factory成为一个可注入的依赖之外,您还可以确保在与mock when(vodkaFactory.getVodka()).thenReturn(vodka);交互之前训练它worker.work();

您要测试的代码中存在逻辑错误。因为您在Worker类中创建了VodkaFactory实例,而且您已将该字段设置为私有。

最好的解决方案是从类外部传递对VodkaFactory的引用。

public class Worker {          
    private VodkaFactory vodkaFactory;
    public void work() {
                   Vodka vodka = vodkaFactory.getVodka();
                   vodka.drink();
     }
     public void setVodkaFactory(VodkaFactory vf) {
           vodkaFactory = vf;
     }
}

现在,在@Test中,您可以使用setVodkaFactory setter传递模拟的VodkaFactory实例。

下面是一个完整的JMockit单元测试,它独立于Vodka依赖性的实现来执行Worker#work()方法:

@Test
public void workTest(@Mocked final Vodka mockBeverage)
{
    new Worker().work();
    new Verifications() {{ mockBeverage.drink(); times = 1; }};
}

最新更新