我们遇到了Mockito非常讨厌的问题。
代码:
public class Baz{
private Foo foo;
private List list;
public Baz(Foo foo){
this.foo = foo;
}
public void invokeBar(){
list = Arrays.asList(1,2,3);
foo.bar(list);
list.clear();
}
}
public class BazTest{
@Test
void testBarIsInvoked(){
Foo mockFoo = mock(Foo.class);
Baz baz = new Baz(mockFoo);
baz.invokeBar();
verify(mockFoo).bar(Arrays.asList(1,2,3));
}
}
这会导致错误消息,如:
Arguments are different! Wanted:
foo.bar([1,2,3]);
Actual invocation has different arguments:
foo.bar([]);
刚刚发生了什么:
Mockito将引用记录到list
而不是list
的副本,因此在上面的代码中,Mockito验证修改后的版本(空列表, []
)而不是在调用期间实际传递的那个([1,2,3]
)!
问题:
除了执行下面的防御性复制之外,是否有任何优雅而干净的解决方案(实际上有所帮助,但我们不喜欢这种解决方案)?
public void fun(){
list = Arrays.asList(1,2,3);
foo.bar(new ArrayList(list));
list.clear();
}
我们不想修改正确的生产代码和降低其性能,只是为了解决测试中的技术问题。
我在这里问这个问题,因为它似乎可能是Mockito的常见问题。或者我们只是做错了什么?
PS。这不是真正的代码,所以请不要问为什么我们创建一个列表然后清除它等。在实际代码中我们真的需要做类似的事情: - )。
答案 0 :(得分:12)
此处的解决方案是使用自定义答案。两个代码示例:第一个是使用的测试类,第二个是测试。
首先,测试类:
private interface Foo
{
void bar(final List<String> list);
}
private static final class X
{
private final Foo foo;
X(final Foo foo)
{
this.foo = foo;
}
void invokeBar()
{
// Note: using Guava's Lists here
final List<String> list = Lists.newArrayList("a", "b", "c");
foo.bar(list);
list.clear();
}
}
进行测试:
@Test
@SuppressWarnings("unchecked")
public void fooBarIsInvoked()
{
final Foo foo = mock(Foo.class);
final X x = new X(foo);
// This is to capture the arguments with which foo is invoked
// FINAL IS NECESSARY: non final method variables cannot serve
// in inner anonymous classes
final List<String> captured = new ArrayList<String>();
// Tell that when foo.bar() is invoked with any list, we want to swallow its
// list elements into the "captured" list
doAnswer(new Answer()
{
@Override
public Object answer(final InvocationOnMock invocation)
throws Throwable
{
final List<String> list
= (List<String>) invocation.getArguments()[0];
captured.addAll(list);
return null;
}
}).when(foo).bar(anyList());
// Invoke...
x.invokeBar();
// Test invocation...
verify(foo).bar(anyList());
// Test arguments: works!
assertEquals(captured, Arrays.asList("a", "b", "c"));
}
当然,能够编写这样的测试需要你能够注入你的“外部对象”足够的状态,以便测试有意义......这里相对容易。