【问题标题】:Mockito - Is there a matcher for "value not in List"?Mockito - 是否有“不在列表中的值”的匹配器?
【发布时间】:2018-08-14 14:36:46
【问题描述】:

我目前有一个模拟,它具有针对特定输入集的特定行为。 其他所有输入都应返回特定响应。

例如:

    Mockito.when(classInstance.myFunc(Mockito.eq("Option1"))).thenReturn(answer1);
    Mockito.when(classInstance.myFunc(Mockito.eq("Option2"))).thenReturn(answer2);
    Mockito.when(classInstance.myFunc(Mockito.eq("Option3"))).thenReturn(answer3);
    Mockito.when(classInstance.myFunc(Mockito.eq("Option4"))).thenReturn(answer4);

    // Return defaultAnswer if and(and(not("Option1"), not("Option2")), and(not("Option3"), not("Option4")))
    Mockito.when(classInstance.myFunc(AdditionalMatchers.and(AdditionalMatchers.and(AdditionalMatchers.not(Mockito.eq("Option1")), AdditionalMatchers.not(Mockito.eq("Option2")), AdditionalMatchers.and(AdditionalMatchers.not(Mockito.eq("Option3")), AdditionalMatchers.not(Mockito.eq("Option4")))).thenReturn(defaultAnswer);

我最大的麻烦是and(and(not("Option1"), not("Option2")), and(not("Option3"), not("Option4"))) 行的复杂性。

我真的希望有一种更简单的方法来指定“其他所有内容”或只是“不在列表中:[“option1”,...]”的条件

是否有“组内”或类似内容的匹配器?

【问题讨论】:

  • 如果在其他 eq() 模拟之后只使用 anyString() 会发生什么?

标签: java unit-testing junit mockito


【解决方案1】:

您可以通过显式定义默认值来使其更具可读性,然后用后续的存根“覆盖”它:

when(classInstance.myFunc(any()).thenReturn(defaultAnswer);
when(classInstance.myFunc("Option1").thenReturn(answer1);
when(classInstance.myFunc("Option2").thenReturn(answer2);
...

或者你可以使用 MockitoHamcrest 和 Hamcrest 的核心匹配器来简化它,例如:

when(classInstance.myFunc(argThat(is(allOf(not("Option1"), not("Option2"), ...))))
    .thenReturn(...)

或者您可以使用 MockitoHamcrest 和您自己的 Hamcrest 匹配器。

【讨论】:

  • 巧妙的+1。但是第一种方法确实有一个缺点。如果您修改代码以在 any() 匹配器之前为相同的模拟方法添加模拟记录,它将被 any() 匹配器覆盖。
  • 我认为这是否是一个缺点值得商榷。我使用的一个常见模式是在@Before 中定义这样的“any()”默认值存根,然后在具有测试相关返回值或验证其参数的测试本身中用存根/模拟覆盖这些存根。这有助于保持高特异性、低噪音的测试。如果像您建议的那样,之前的存根继续对后续存根产生一些影响,我认为这不会起作用。
  • @Before钩子方法中的默认mock记录很好!
【解决方案2】:

为什么不简单地使用Mockito.matches(boolean),例如:

 import static org.mockito.Mockito.*;

 Mockito.when(classInstance.myFunc(matches("Option[^1-4]"))
        .thenReturn(defaultAnswer);

您也可以使用Mockito.argThat()
要过滤掉一些整数值(如您的评论中所建议),您可以编写:

 import static org.mockito.Mockito.*;

 List<Integer> toNotMatchList = Arrays.asList(1, 2, 3, 4) ;
 Mockito.when(classInstance.myFunc(argThat(i -> !toNotMatchList.contains(i))
        .thenReturn(defaultAnswer);

或更直接:

Mockito.when(classInstance.myFunc(argThat(i -> !Arrays.asList(1, 2, 3, 4).contains(i))
        .thenReturn(defaultAnswer);

【讨论】:

  • 不错的一个!简单的解决方案总是比复杂的解决方案好。我仍然认为应该小心使用这样的多个规范,但至少你的代码是人类可读的!
  • 谢谢。那可能会奏效。但实际上整数需要它,“Option1”只是一个例子
  • @GhostCat 我完全同意你的看法。为模拟指定过多的固定装置不是直接可读和可维护的。
  • @GuyKhmel 欢迎您。在这种情况下,只需使用ArgumentMatcher。不需要harmcrest 或任何第三个库,只需一个最近的Mockito 版本(2)。我更新了。
  • Java 8 流和 Mockito 2 现在可以实现这一点非常酷。但是,使用 Hamcrest 匹配器的优势仍然是使用它们而不是直接谓词可以获得更好的失败消息。
【解决方案3】:

我目前有一个模拟,它具有针对特定输入集的特定行为。其他所有输入都应返回特定响应。

就像,错了。

单元测试的目的是让您快速发现错误。为了了解发生了什么,您希望单元测试尽可能地“自包含”。您阅读了测试,也许是设置方法,并且您了解正在发生的事情(或至少:“进入”您的测试代码)。

相反,拥有多个规范来涵盖多个案例对此无济于事。你不想要那个。

相反,您需要尽可能少的规格。如果你的 mock 看到不同的输入,并且应该返回不同的结果,那么这应该是你在 per 测试用例中所做的事情。在您的设置方法中,不是“一般”。

所以明显的非答案:避免有多个这样的规范。

【讨论】:

  • 我喜欢你不回答的问题。我最近写了两个:)
  • 你是对的,感觉它会迫使你为你的测试用例定义测试用例
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2015-02-02
  • 1970-01-01
相关资源
最近更新 更多