单元测试强制要求在隔离测试单元。 为了实现这一点,通常的共识是使用DI以分离的方式设计我们的类。 在这种范式中,无论是否使用框架,使用编译时还是运行时编译,对象实例化都是专用工厂的责任。 特别是,这意味着new
关键字应仅在这些工厂中使用。
但是,有时不适合拥有专门的工厂。 将窄范围的实例注入到更大范围的实例时,就是这种情况。 我最近偶然发现的一个用例是事件总线,它是这样的代码:
publicclassSample{
privateEventBuseventBus;
publicSample(EventBuseventBus){
this.eventBus=eventBus;
}
publicvoiddone(){
Resultresult=computeResult()
eventBus.post(newDoneEvent(result));
}
privateResultcomputeResult(){
...
}
}
使用运行时DI框架(例如Spring框架),并且如果DoneEvent没有参数,则可以将其更改为查找方法模式。
publicvoiddone(){
eventBus.post(getDoneEvent());
}
publicabstractDoneEventgetDoneEvent();
不幸的是,这种论点只是阻止我们使用这个漂亮的把戏。 无论如何,它不能通过运行时注入来完成。 但是,这并不意味着不应该测试done()
方法。 问题不仅在于如何断言在调用该方法时是否在总线上发布了新的DoneEvent
,而且还检查了包装的结果。
经验丰富的软件工程师可能知道Mockito.any(Class<?>)
方法。 可以这样使用:
publicvoiddoneShouldPostDoneEvent(){
EventBuseventBus=Mockito.mock(EventBus.class);
Samplesample=newSample(eventBus);
sample.done();
Mockito.verify(eventBus).post(Mockito.any(DoneEvent.class));
}
在这种情况下,我们确保将正确类型的事件发布到队列中,但是我们不确定结果是什么。 而且,如果无法确定结果,则对代码的置信度会降低。 Mockito进行营救。 Mockito提供捕获,就像参数的占位符一样。 上面的代码可以这样更改:
publicvoiddoneShouldPostDoneEventWithExpectedResult(){
ArgumentCaptor<DoneEvent>captor=ArgumentCaptor.forClass(DoneEvent.class); (1)
EventBuseventBus=Mockito.mock(EventBus.class);
Samplesample=newSample(eventBus);
sample.done();
Mockito.verify(eventBus).post(captor.capture()); (2)
DoneEventevent=captor.getCapture(); (3)
assertThat(event.getResult(),is(expectedResult)); (4)
}
- 我们创建一个新的
ArgumentCaptor
。 - 我们用
captor.capture()
替换any()
用法,技巧就完成了。 - 然后,结果由Mockito捕获,并可以通过
captor.getCapture()
。 - 使用Hamcrest,确保结果是预期的结果。
翻译自: https://blog.frankel.ch/improve-your-tests-with-mockitos-capture/