Mockito 验证模拟对象的最后一次调用
Mockito verify the last call on a mocked object
我有一些逻辑需要测试,例如:
{
...
A.add("1");
...
A.add("what ever");
...
A.add("2");
A.delete("5");
...
}
我已经在我的测试中模拟了 A,我可以测试添加方法在参数(“2”)上被调用一次,例如:
Mockito.verify(mockedA).add("2");
我的问题是如何测试是否可以验证对方法 add 的最后一次调用是 add("2") 而不是其他参数。
因为如果有人不小心在最后添加了另一个调用,如 add("3"),上面的测试无法捕获。请注意,之后我们不关心 A 上的其他方法调用。我们也不关心调用方法的次数,调用方法的顺序。 这里的关键点是我们是否可以验证某个 mockedObject 的某个方法上的最后一个 true 参数。
如果你问为什么需要这样的功能,我会说在现实世界中我们可能需要处理一些逻辑来设置一些东西并且最后一个设置获胜,并且为了避免有人不小心设置了一些其他东西出乎意料,我想用我们的 UT 来捕捉它。为了不让测试过于复杂和整洁,所以我只希望验证对象某个方法的最后一次调用,而不是验证 order/noMoreInteractions/AtMostTimes 之类的东西。
关于调用顺序
默认情况下,Mockito.verify()
与调用顺序无关。
考虑到这一点,将 mock 包装在一个 InOrder
实例中,并对该实例执行调用验证。
关于不再互动
如果在您要验证的方法之后不再调用模拟,您可以使用 Mockito.verifyNoMoreInteractions(Object... mocks)
检查任何给定模拟是否有任何未经验证的交互,例如:
InOrder inOrder = Mockito.inOrder(mockedA);
inOrder.verify(mockedA).add("1");
inOrder.verify(mockedA).add("2");
Mockito.verifyNoMoreInteractions(mockedA);
如果在您要验证的方法之后仍然可以调用模拟,您可以在验证调用后添加到 verify(T mock, VerificationMode mode)
,方法是传递一个 VerificationMode
来检查最多 2 次调用进行了。
InOrder inOrder = Mockito.inOrder(mockedA);
inOrder.verify(mockedA).add("1");
inOrder.verify(mockedA).add("2");
Mockito.verify(mockedA, Mockito.atMost(2)).add(Mockito.anyString());
警告你的想法和这种嘲讽的方式
Since the test above can't catch if somebody by accident adds another
call such as add("3") in the last.
Mockito 提供了一个强大而广泛的工具包来处理模拟。一些功能,例如验证,更具体地说,验证没有检测到关于模拟或模拟的特定方法的更多交互 使您的测试更难阅读和维护。
此外,目前您想要检查模拟上的调用是否按特定顺序执行。但是您通常只想根据 business/logic 场景的需要使用这些检查,而不是技术调用。
例如,假设在被测试的方法中,您有一种情况,出于业务原因,模拟方法被调用 3 次,而另一种情况是,模拟方法被调用 2 次。检查它是否仅被调用 2 次而不是在有两次预期调用的情况下是否更多是有意义的。
但总的来说,您应该谨慎,您的单元测试不会过度使用模拟验证,它可能看起来像是对流程描述的断言,而不是对 behavior/logic 的断言。
在我看来,您正在模拟数据 class。根据我的经验,最好保留(有状态)数据 classes 和模拟(无状态)服务。这样,您就可以验证被测方法是否生成了正确的数据,而不仅仅是验证一系列调用。连同测试数据构建器(例如,使用构建器模式,可以轻松地实例化具有某些默认状态的数据 classes),编写测试变得非常容易。
如果您确实需要模拟,测试所需内容的唯一方法是使用 InOrder
,并验证模拟中的每个调用,并以 verifyNoMoreInteractions
结束。
感谢 @staszko032,受到 ArgumentCaptor 的启发,我们可以使用 captor 的 getValue 而不是 getAllValues 并验证序列,因为 captor 的 getValue 总是获得最后一个真实参数。我们可以这样做:
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
Mockito.verify(mockedA, Mockito.atLeastOnce()).add(captor.capture());
Assert.assertEquals("2", captor.getValue());
我有一些逻辑需要测试,例如:
{
...
A.add("1");
...
A.add("what ever");
...
A.add("2");
A.delete("5");
...
}
我已经在我的测试中模拟了 A,我可以测试添加方法在参数(“2”)上被调用一次,例如:
Mockito.verify(mockedA).add("2");
我的问题是如何测试是否可以验证对方法 add 的最后一次调用是 add("2") 而不是其他参数。
因为如果有人不小心在最后添加了另一个调用,如 add("3"),上面的测试无法捕获。请注意,之后我们不关心 A 上的其他方法调用。我们也不关心调用方法的次数,调用方法的顺序。 这里的关键点是我们是否可以验证某个 mockedObject 的某个方法上的最后一个 true 参数。
如果你问为什么需要这样的功能,我会说在现实世界中我们可能需要处理一些逻辑来设置一些东西并且最后一个设置获胜,并且为了避免有人不小心设置了一些其他东西出乎意料,我想用我们的 UT 来捕捉它。为了不让测试过于复杂和整洁,所以我只希望验证对象某个方法的最后一次调用,而不是验证 order/noMoreInteractions/AtMostTimes 之类的东西。
关于调用顺序
默认情况下,Mockito.verify()
与调用顺序无关。
考虑到这一点,将 mock 包装在一个 InOrder
实例中,并对该实例执行调用验证。
关于不再互动
如果在您要验证的方法之后不再调用模拟,您可以使用 Mockito.verifyNoMoreInteractions(Object... mocks)
检查任何给定模拟是否有任何未经验证的交互,例如:
InOrder inOrder = Mockito.inOrder(mockedA);
inOrder.verify(mockedA).add("1");
inOrder.verify(mockedA).add("2");
Mockito.verifyNoMoreInteractions(mockedA);
如果在您要验证的方法之后仍然可以调用模拟,您可以在验证调用后添加到 verify(T mock, VerificationMode mode)
,方法是传递一个 VerificationMode
来检查最多 2 次调用进行了。
InOrder inOrder = Mockito.inOrder(mockedA);
inOrder.verify(mockedA).add("1");
inOrder.verify(mockedA).add("2");
Mockito.verify(mockedA, Mockito.atMost(2)).add(Mockito.anyString());
警告你的想法和这种嘲讽的方式
Since the test above can't catch if somebody by accident adds another call such as add("3") in the last.
Mockito 提供了一个强大而广泛的工具包来处理模拟。一些功能,例如验证,更具体地说,验证没有检测到关于模拟或模拟的特定方法的更多交互 使您的测试更难阅读和维护。
此外,目前您想要检查模拟上的调用是否按特定顺序执行。但是您通常只想根据 business/logic 场景的需要使用这些检查,而不是技术调用。
例如,假设在被测试的方法中,您有一种情况,出于业务原因,模拟方法被调用 3 次,而另一种情况是,模拟方法被调用 2 次。检查它是否仅被调用 2 次而不是在有两次预期调用的情况下是否更多是有意义的。
但总的来说,您应该谨慎,您的单元测试不会过度使用模拟验证,它可能看起来像是对流程描述的断言,而不是对 behavior/logic 的断言。
在我看来,您正在模拟数据 class。根据我的经验,最好保留(有状态)数据 classes 和模拟(无状态)服务。这样,您就可以验证被测方法是否生成了正确的数据,而不仅仅是验证一系列调用。连同测试数据构建器(例如,使用构建器模式,可以轻松地实例化具有某些默认状态的数据 classes),编写测试变得非常容易。
如果您确实需要模拟,测试所需内容的唯一方法是使用 InOrder
,并验证模拟中的每个调用,并以 verifyNoMoreInteractions
结束。
感谢 @staszko032,受到 ArgumentCaptor 的启发,我们可以使用 captor 的 getValue 而不是 getAllValues 并验证序列,因为 captor 的 getValue 总是获得最后一个真实参数。我们可以这样做:
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
Mockito.verify(mockedA, Mockito.atLeastOnce()).add(captor.capture());
Assert.assertEquals("2", captor.getValue());