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

时间:2013-03-11 22:56:11

标签: java unit-testing tdd mockito legacy

我正在使用mockito进行练习,但我对如何测试依赖于本地对象中方法调用的方法感到困惑。 请参阅以下示例:

public class Worker {          

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

这名工人不喜欢自己的工作,而是喜欢喝酒。但是我想补充一个测试来证明他在工作时喝酒。但是没有办法这样做,因为我必须验证调用方法工作时调用方法drink()。我认为你同意我的意见,这是不可能测试的,所以我需要在开始测试之前打破依赖。 这是我的第一个疑问,你认为打破这种依赖的最佳方法是什么? 如果我只是将伏特加对象的范围更改为全局,我认为不会很好(我不想将它暴露给类的其他部分)。我想创建一个像这样的工厂:

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

我嘲笑工厂,何时会检测到工厂创建了一个新的伏特加对象。但是当我想验证该方法调用方法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()的方法用于模拟方法调用,你认为它在这种情况下有用吗? 我应该如何使用它?

更新

我正在遵循建议,以便在when()之前调用work(),同时我也尝试允许在课堂外设置工厂:

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

的行

很抱歉,我仍然对这个问题感到困惑。

5 个答案:

答案 0 :(得分:3)

你的工人在这里创建了自己的工厂类:

private VodkaFactory vodkaFactory = new VodkaFactory();

您正在创建的模拟与工作者实例完全分离,因此缺乏交互。为了使其工作,工厂必须从外部注入工人",通过constructor injection说明。

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

如JB Nizet在评论中所述,您的模拟设置在调用work之后出现。为了使事情正确,请在调用任何使用它的代码之前注入模拟并进行设置。

答案 1 :(得分:3)

你需要设置你的伏特加工厂:

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

答案 2 :(得分:1)

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

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

public class Worker {          

    private VodkaFactory vodkaFactory;

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

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

}

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

答案 3 :(得分:1)

这是一个评论而不是一个答案。除了使工厂成为可注射依赖项之外,您还可以确保在与模拟when(vodkaFactory.getVodka()).thenReturn(vodka);交互之前训练模拟worker.work();

答案 4 :(得分:1)

以下是一个完整的JMockit单元测试,它与Worker#work()依赖项的实现隔离开来运行Vodka方法:

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

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