Mockito如何在thenReturn块中处理具有多个参数的重叠匹配器

时间:2016-10-25 17:27:56

标签: java mockito

我有一块测试代码试图在通用情况下在后续调用中返回两个值,但在特定情况下只返回与该情况相关的值。代码看起来像:

ImportError                               Traceback (most recent call last)
/home/usr/script.py in <module>()
      1 import pymc
----> 2 from MCMC2 import ExoData
      ...

/home/usr/MCMC2/__init__.py in <module>()
      ...
----> 4 import MCMC2.main_script
      ...

/home/usr/MCMC2/main_script.py in <module>()
      1 try: from Models import model_main
----> 2 except: from .Models import model_main
      ...

/home/usr/MCMC2/Models/__init__.py in <module>()
----> 1 import Models.model_main
      ...

ImportError: No module named 'Models'

预期的行为是,在调用when(mockObject.method(anyString())).thenReturn(string1, string2); when(mockObject.method(eq("expectedInput1"))).thenReturn(string1); when(mockObject.method(eq("expectedInput2"))).thenReturn(string2); mockObject.method("foo")时,应分别返回mockObject.method("bar")string1,但测试实际上看到{{1}的两个响应}}。这是string2中的错误吗?或者我误解了string2模式匹配。

我的假设是匹配的最后一个模式是返回的内容,但是当经历该过程时,Mockito是否分别处理第一个Mockito块中的每个参数?有没有办法绕过这种行为?

当我在调用时注释掉后两个时,模拟的行为与预期一致,所以我假设有一些关于重叠匹配器行为的特定内容。

修改:这是Mockito版本thenReturn

3 个答案:

答案 0 :(得分:10)

我今天遇到了这个问题。这是由于调用mock来设置实际消耗已存在的存根的存根。

在此示例中,将第一行更改为

when(mock.call(anyString())).thenReturn("","",string1,string2)

当您设置其他模拟返回时,这将给您两个空白响应,将string1作为第一个有用的返回值。

还尝试doReturn替代方案,我认为可能没有这些问题:

doReturn(string1,string2).when(mock).call(anyString());

这会在设置过程中以不同方式使用存根。

所以我对此做了更多的研究。根据OP的问题,这是我正在玩的功能:

    Function<String, String> function = mock(Function.class);
    when(function.apply(anyString())).thenReturn("A","B","C");
    when(function.apply("Jim")).thenReturn("Jim");
    when(function.apply("Bob")).thenReturn("Bob");

    assertThat(function.apply("Jim")).isEqualTo("Jim");
    assertThat(function.apply("Bob")).isEqualTo("Bob");
    assertThat(function.apply("")).isEqualTo("A");
    assertThat(function.apply("")).isEqualTo("B");
    assertThat(function.apply("")).isEqualTo("C");
    assertThat(function.apply("")).isEqualTo("C");

上述内容在isEqualTo("A")失败,因为设置JimBob的模拟的两次调用会消耗提供给anyString()的列表中的返回值。

您可能很想重新排序when子句,但这会失败,因为anyString()会取代特殊情况,因此也会失败。

以上版本的上述DOES按预期工作:

    when(function.apply(anyString())).thenReturn("A","B","C");

    doReturn("Jim")
        .when(function)
        .apply("Jim");
    doReturn("Bob")
        .when(function)
        .apply("Bob");

    assertThat(function.apply("Jim")).isEqualTo("Jim");
    assertThat(function.apply("Bob")).isEqualTo("Bob");
    assertThat(function.apply("")).isEqualTo("A");
    assertThat(function.apply("")).isEqualTo("B");
    assertThat(function.apply("")).isEqualTo("C");
    assertThat(function.apply("")).isEqualTo("C");

这是因为doReturn技术用于修改飞行中预先存在的模拟,实际上并不涉及在模拟上调用方法来设置模拟。

您可以使用doReturn进行所有设置,而不是在when ... thenReturndoReturn之间混合.. when .. {{1 }}。碰巧,这有点丑陋:

function()

没有方便的 doReturn("A").doReturn("B").doReturn("C") .when(function) .apply(anyString()); 功能让您按顺序指定多个返回。以上内容已经过测试,确实有效。

答案 1 :(得分:2)

解决此问题的一种方法是使用正则表达式以避免重叠:

when(mockObject.method(eq("expectedInput1"))).thenReturn(string1);
when(mockObject.method(eq("expectedInput2"))).thenReturn(string2);
// Match with any input string that doesn't contain expectedInput1 neither expectedInput2
when(mockObject.method(matches("((?!expectedInput1|expectedInput2).)*")))
    .thenReturn(string1, string2);

示例:

System.out.println("expectedInput1=" + mockObject.method("expectedInput1"));
System.out.println("expectedInput2=" + mockObject.method("expectedInput2"));
System.out.println("foo=" + mockObject.method("foo"));
System.out.println("bar=" + mockObject.method("bar"));
System.out.println("bar=" + mockObject.method("bar"));

输出:

expectedInput1=string1
expectedInput2=string2
foo=string1
bar=string2
bar=string2

另一种方法是实施ArgumentMatcher仍然可以避免重叠:

when(mockObject.method(eq("expectedInput1"))).thenReturn(string1);
when(mockObject.method(eq("expectedInput2"))).thenReturn(string2);
when(
    mockObject.method(
        argThat(
            new ArgumentMatcher<String>(){
                @Override
                public boolean matches(final Object argument) {
                    return !"expectedInput1".equals(argument) 
                        && !"expectedInput2".equals(argument);
                }
            }
        )
    )
).thenReturn(string1, string2);

另一种方法是使用以下内容实现Answer

when(mockObject.method(anyString())).thenAnswer(
    new Answer<String>() {
        Iterator<String> it = Arrays.asList(string1, string2).iterator();
        String result;
        @Override
        public String answer(final InvocationOnMock invocation) throws Throwable {
            String argument = (String) invocation.getArguments()[0];
            switch (argument) {
                case "expectedInput1" :
                    return string1;
                case "expectedInput2" :
                    return string2;
                default:
                    if (it.hasNext()) {
                        result = it.next();
                    }
                    return result;
            }
        }
    }
);

答案 2 :(得分:2)

很难说这是一个错误还是一个功能......问题是,当你致电mockObject.method(eq("expectedInput1"))执行第二次存根时,第一个存根已经到位。所以调用返回string1,然后无用地传递给when。后续调用返回string2,其中包括在实际测试期间调用最后一个存根和稍后的调用。

我几乎看不到任何优雅的方式,除了使用像@Nicolas建议的自定义Answer之外,虽然它看起来像是一种矫枉过正。或许,您可以使用自定义匹配器而不是anyString(),这实际上会说“任何字符串除了那两个”。这样你就不会有一个匹配器与另一个匹配器。

P上。 S.现在@Nicolas编辑了他的答案,正则表达看起来就像我的意思。除此之外,您根本不需要实现自定义匹配器。