打破局部依赖关系以unit testingvoid方法
我正在使用mockito进行练习,但我对如何测试依赖于本地对象中方法调用的方法有点困惑。 请参阅以下示例:
public class Worker { public void work() { Vodka vodka = new Vodka(); vodka.drink(); } }
这个工人不喜欢做自己的工作,而是喜欢喝酒。 但是我想补充一个测试来certificate他在工作时喝酒。 但是没有办法这样做,因为我必须validation调用方法工作时调用方法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(); }
我嘲笑工厂,何时会发现工厂创建了一个新的伏特加对象。 但是当我想validation该方法调用方法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.
我没有正确存根或者我做错了什么。 你能帮我完成一下这个测试,还能告诉我测试这些无法测试的方法的最佳方法是什么?
我知道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()
抱歉,我仍然对这个问题感到困惑。
您的工人在这里创建自己的工厂类:
private VodkaFactory vodkaFactory = new VodkaFactory();
您正在创建的模拟与工作器实例完全分离,因此缺少交互。 为了使其工作,工厂必须从“外部”注入工人,比如通过构造注入 。
如果这是遗留代码,您可以使用reflection将私有工厂实例替换为模拟实例。
正如JB Nizet在评论中所指出的,你的模拟设置是在已经调用work
之后发生的。 为了使事情正确 ,请在调用任何使用它的代码之前注入mock并进行设置。
你需要设置你的伏特加工厂:
@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(); }
这是一个评论而不是一个答案。 除了使工厂成为可注射的依赖项之外,你还可以确保训练你的模拟when(vodkaFactory.getVodka()).thenReturn(vodka);
在与之交互之前worker.work();
您尝试测试的代码中存在逻辑错误。 因为您已在Worker
类中创建了VodkaFactory
实例,而且您已将该字段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
实例。
以下是一个完整的JMockitunit testing,它独立于Vodka
依赖项的实现来运行Worker#work()
方法:
@Test public void workTest(@Mocked final Vodka mockBeverage) { new Worker().work(); new Verifications() {{ mockBeverage.drink(); times = 1; }}; }