【问题标题】:Mockito verify the last call on a mocked objectMockito 验证对模拟对象的最后一次调用
【发布时间】:2018-11-03 08:23:46
【问题描述】:

我有一点逻辑需要测试,比如:

{
    ...
    A.add("1");
    ...
    A.add("what ever");
    ...
    A.add("2");
    A.delete("5");
    ...
}

我已经在我的测试中模拟了 A,我可以测试 add 方法在参数(“2”)上调用一次,例如:

Mockito.verify(mockedA).add("2");

我的问题是如何测试我是否可以验证对方法 add 的最后调用是 add("2") 而不是其他参数。

由于上面的测试无法捕捉到是否有人在最后一个意外添加了另一个调用,例如 add("3")。请注意,之后我们不再关心 A 上的其他方法调用。我们也不关心方法调用的次数,方法调用的顺序。 这里的关键是我们是否可以验证某个 mockedObject 的某个方法上的最后一个 true 参数。

如果你问为什么需要这样的功能,我会说在现实世界中我们可能需要处理一些设置某些东西并且最后一组获胜的逻辑,并且为了避免有人意外设置其他一些意想不到的东西并且我想用我们的 UT 来捕捉这个。而且为了不让测试过于复杂和整洁,所以我只希望验证对象的某个方法的最后一次调用,而不是验证诸如 order/noMoreInteractions/AtMostTimes 之类的东西。

【问题讨论】:

  • 见stackoverflow.com/questions/8504074/…>,也许你会觉得有帮助。
  • 谢谢,这很有帮助。

标签: java mockito call verify


【解决方案1】:

关于调用顺序

默认情况下,Mockito.verify() 与调用顺序无关。
考虑到这一点,将模拟包装在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());

关于你的想法和这种嘲弄方式的警告

由于上面的测试无法捕捉到是否有人意外添加了另一个 最后调用 add("3") 等。

Mockito 提供了一个强大而广泛的工具包来处理模拟。某些功能,例如验证,尤其是验证没有检测到关于模拟或模拟的特定方法的更多交互使您的测试更难以阅读和维护
此外,目前您希望检查模拟上的调用是否按特定顺序执行。但您通常只想根据业务/逻辑场景的需要使用这些检查,而不是技术调用。
例如,假设在测试方法中,由于业务原因,模拟方法被调用 3 次,另一种情况是模拟方法被调用 2 次。在预期调用两次的情况下,检查它是否只被调用了 2 次而不是更多次是有意义的。
但总的来说,您应该小心,您的单元测试不会过度使用模拟验证,这可能看起来像是对流程描述的断言,而不是对行为/逻辑的断言。

【讨论】:

  • 这个不行,因为逻辑可以在A上调用A.delete()等其他方法,这里的关键是验证一个对象的某个METHOD的最后一个真争论。
  • @hongchangfirst 好的。我更新以区分两种情况。
  • 谢谢。虽然这仍然不是我们所期望的。如果 add 方法调用了两次以上怎么办?关键是我们能否验证对某个对象的某个方法的最后一次调用?
  • @hongchangfirst 好的。我理解你的意思。我更新了,但我真的想知道在测试中增加如此复杂性是否有意义。我补充一点关于我的想法。
  • 因为我想避免复杂性,所以我想拥有这样的功能。 At Most Times 现在可以工作,但如果有人将第一个呼叫移到最后一个呼叫,它就无法捕捉到。您的 UT 通过,但您的服务中断。或者你可以争辩说我们可以使用 inOrder 来测试序列。但是我会说如果有人删除了第一个并且该方法仍然没有破坏怎么办。但是你的UT坏了。总的来说,这不是我们所期望的。
【解决方案2】:

在我看来,您正在模拟一个数据类。根据我的经验,最好保留(有状态)数据类和模拟(无状态)服务。这样,您可以验证被测方法是否产生了正确的数据,而不仅仅是验证一系列调用。与 testdata 构建器一起使用(例如使用构建器模式,可以很容易地用一些默认状态实例化您的数据类),编写测试变得非常容易。

如果您确实需要模拟,测试所需内容的唯一方法是使用InOrder,并验证模拟上的每个调用,并以verifyNoMoreInteractions 结尾。

【讨论】:

  • 我曾想过使用真实对象而不是模拟对象来测试我们拥有的最后一个状态。这不是一个坏主意。当课程微不足道时,这是完美的。而如果这个类将它委托给另一个类,而这个类也委托给其他类,那么复杂,有很多依赖关系,那么做到这一点并不容易。而且它更像是测试 A 的逻辑,而不是测试使用 A 的逻辑。
  • 你的测试会“扩展”一点,这是正确的。但通常,数据类非常简单。使用构建器模式也可以减轻一些复杂性,我过去曾成功使用过它。如果您的数据类是Person,并且您只想要默认人,那么您可以使用new PersonBuilder().build();。然后你会得到一个名为“Test Testman”或类似名称的人。如果您想覆盖特定测试的某些值,请执行 new PersonBuilder().firstName("Donald").lastName("Duck").address(new AddressBuilder().city("Duckburg").build()).build(); 之类的操作
  • 创建这些构建器需要一些工作,但是一旦你有了它们,使用它们编写测试就会变得非常愉快。验证状态比验证多个方法调用要容易得多。
  • 考虑这样的情况:如果你的类将它委托给另一个类,而这个类也委托给其他类,很复杂,有很多依赖关系?
  • 我试图展示如何使用示例中的 Person-Address 处理类图。您将需要为树中的每个类创建一个构建器(但可能可以根据需要创建它们,而不是一次性创建)。当然,如果您的数据类图由数百个类组成,那么它可能不是最好的方法。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-05
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 2013-06-09
    • 1970-01-01
    相关资源
    最近更新 更多