用Mockito
检查正确参数是否正确的最佳方法是什么?
考虑下一个单元测试:
@Test
public void getProjectByIdTest() {
Long projectId = 1L;
ProjectEntity expectedProject = mock(ProjectEntity.class);
when(projectRepository.findOne(anyLong())).thenReturn(expectedProject);
assertThat(projectService.getById(projectId), is(expectedProject));
verify(projectRepository).findOne(projectId);
}
在这里,我们检查projectService
是否使用verify
显式地将其参数传递到正确的位置。
现在检查这个单元测试:
@Test
public void getProjectByIdTest() {
Long projectId = 1L;
ProjectEntity expectedProject = mock(ProjectEntity.class);
when(projectRepository.findOne(projectId)).thenReturn(expectedProject);
assertThat(projectService.getById(projectId), is(expectedProject));
}
它还检查projectService
是否将其参数传递到了正确的位置,但隐式地传递给了when
(因此,如果projectService
实际上将向projectRepository.findOne()
传递一些随机数,则assertThat
将失败,因为mock将返回错误的值)。
那么应该如何做到这一点呢?在我看来,如果没有verify
,这个测试就失去了一些清晰度;但从另一方面来说它更短。
您想测试什么你应该试着测试你的测试单元(这里是你的测试方法)是否有效,并且只验证对通用合同很重要的交互。
@Test
public void getProjectByIdTest_withVerify() {
Long projectId = 1L;
ProjectEntity expectedProject = mock(ProjectEntity.class);
// Your simulated dependency returns expectedProject for every ID?
when(projectRepository.findOne(anyLong())).thenReturn(expectedProject);
assertThat(projectService.getById(projectId), is(expectedProject));
// Is it a requirement of your test that this method must be called?
// Maybe your system someday calls a "findAll" method, or caches values.
verify(projectRepository).findOne(projectId);
}
比较:
@Test
public void getProjectByIdTest() {
Long projectId = 1L;
ProjectEntity expectedProject = mock(ProjectEntity.class);
// You return the expectedProject when asked for. Everything else returns null.
when(projectRepository.findOne(projectId)).thenReturn(expectedProject);
// You check that the return value is correct, which implies the call succeeded.
assertThat(projectService.getById(projectId), is(expectedProject));
}
verify
交互肯定有自己的位置,特别是对于ArgumentCaptors和特定方法调用是契约一部分的交互,例如服务器或RPC调用或void方法调用。
要想获得真正权威的观点,请阅读Mockito创始人Szczepan Faber的文章《问和说之间有区别吗?》。它提供了很多细节和见解,说明从期望中可以推断出什么,以及什么值得验证。
小备注。您的mockito行为记录在您的两个示例之间不对称:
when(projectRepository.findOne(anyLong())).thenReturn(expectedProject);
和
when(projectRepository.findOne(projectId)).thenReturn(expectedProject);
在这两种情况下,您都应该使用when(projectRepository.findOne(projectId))
来查看如何进行断言。
那么应该如何做到这一点呢?在我看来,如果没有这个验证这个测试失去了一些清晰度;但从另一方面来说它更短。
就我个人而言,当我想要模拟的方法没有返回类型时,我认为verify()
是一个降级的解决方案
在某些情况下,它至少允许检查是否调用了依赖项以及它所具有的预期参数
如果模拟依赖项的方法返回了一些东西,就像在您的情况下一样,从单元测试的角度来看,第二个解决方案似乎更自然
当你写这样的东西时:
when(projectRepository.findOne(anyLong())).thenReturn(expectedProject);
assertThat(projectService.getById(projectId), is(expectedProject));
verify(projectRepository).findOne(projectId);
您给人的印象是,您希望模拟依赖关系存储库,但也希望检查存储库上的每个调用方法
首先它是多余的,因为如果这个断言是正确的:assertThat(projectService.getById(projectId), is(expectedProject));
,这意味着使用期望的参数调用了存储库,并返回了期望的项目。为什么要用verify(projectRepository).findOne(projectId);
再次检查?
其次,单元测试的目标不是检查是否调用了依赖项上的每个方法。它在断言方面没有带来太多东西,而且您将单元测试与依赖项的api强耦合,从而使测试更加复杂。
UnitTest的第三件最重要的事情是可读性。
测试的读者应该很容易理解测试失败的原因。因此,将mock配置为尽可能宽容,并在验证中检查行为。