我有一个测试代码块,它试图在一般情况下在后续调用中返回两个值,但在特定情况下仅返回与该情况相关的值。代码看起来像这样:
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
和string2
,但测试实际看到的是string2
的两个响应。这是一个bug在Mockito
?还是我误解了Mockito
模式匹配。
我的假设是最后匹配的模式是返回的,但是当经历这个过程时,Mockito
是否分别处理第一个thenReturn
块中的每个参数?有什么办法可以避免这种行为吗?
当我注释掉第二个调用时,模拟的行为与预期的一样,所以我假设重叠匹配器行为有一些特定的东西。
编辑:这是在Mockito
版本1.9.5
我今天遇到了这个问题。这是由于调用模拟来设置存根,实际上消耗了已经存在的存根。
在本例中,将第一行更改为
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")
处失败,因为为Jim
和Bob
设置模拟的两个调用使用了提供给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
…thenReturn
和doReturn
..when
. .function()
。实际上,这有点难看:
doReturn("A").doReturn("B").doReturn("C")
.when(function)
.apply(anyString());
没有方便的varargs
函数让您按顺序指定多个返回。上面的代码已经测试过了,可以正常工作。
很难说这是一个bug还是一个特性…问题是,当您调用mockObject.method(eq("expectedInput1"))
执行第二次存根时,第一次存根已经到位。因此这个调用返回string1
,然后将其无用地传递给when
。随后的调用返回string2
,这包括最后一次存根的调用和实际测试期间的后续调用。
我几乎看不到任何优雅的方法,除了使用自定义的Answer
,就像@Nicolas建议的那样,尽管它看起来有点小题大做。也许,您可以使用自定义匹配器而不是anyString()
,这实际上会说"任何字符串除了那两个"。这样你就不会有一个匹配器与另一个匹配器相交。
p。现在@Nicolas编辑了他的答案,这个正则表达式看起来就是我的意思。除了你根本不需要实现自定义匹配器。
解决这个问题的一种方法是使用正则表达式来避免重叠为下一个:
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;
}
}
}
);