Java Mockito:若传递给mock的参数被修改了怎么办?
我们遇到了莫基托的严重问题 代码:Java Mockito:若传递给mock的参数被修改了怎么办?,java,unit-testing,pass-by-reference,mockito,Java,Unit Testing,Pass By Reference,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 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([]);
刚刚发生的事:
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));
}
}
Mockito记录对列表的引用而不是列表的副本,因此在上面的代码中,Mockito根据修改后的版本(空列表,[]
)而不是调用期间实际传递的版本([1,2,3]
)进行验证
问题:
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));
}
}
除了像下面这样做一个防御性的拷贝(这确实有帮助,但我们不喜欢这个解决方案),还有什么优雅而干净的解决方案吗
我们不想修改更正生产代码,而降低其性能,只是为了解决测试中的技术问题
我在这里问这个问题,因为这似乎是Mockito的常见问题。或者我们只是做错了什么
注:这不是一个真正的代码,所以请不要问我们为什么要创建一个列表,然后清除它等等。在真正的代码中,我们确实需要做类似的事情:-)。这里的解决方案是使用定制的答案。两个代码示例:第一个是使用的测试类,第二个是测试
首先,测试类:
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();
}
}
专用接口Foo
{
无效栏(最终列表);
}
私有静态最终类X
{
私人决赛福福;
X(最终Foo-Foo)
{
this.foo=foo;
}
void invokeBar()
{
//注:此处使用番石榴列表
最终列表=列表。newArrayList(“a”、“b”、“c”);
食物吧(名单);
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"));
}
@测试
@抑制警告(“未选中”)
公共无效fooBarIsInvoked()
{
最终的Foo-Foo=mock(Foo.class);
最终X=新X(foo);
//这是为了捕获调用foo的参数
//FINAL是必需的:非FINAL方法变量不能用于
//在内部匿名类中
捕获的最终列表=新的ArrayList();
//告诉我们,当使用任何列表调用foo.bar()时,我们希望吞下它的
//将元素列在“捕获”列表中
doAnswer(新答案)
{
@凌驾
公共对象应答(最终调用锁调用)
扔掉的
{
最终名单
=(列表)调用.getArguments()[0];
已捕获。addAll(列表);
返回null;
}
}).when(foo.bar)(anyList());
//调用。。。
x、 invokeBar();
//测试调用。。。
验证(foo).bar(anyList());
//测试参数:工作!
资产质量(捕获、数组、asList(“a”、“b”、“c”);
}
当然,能够编写这样的测试需要您能够向“外部对象”注入足够的状态,以便测试有意义。。。这里比较简单。你真的在同一个参数类的不同实例上进行验证吗?@fge是的,我无法访问测试代码中的原始实例,因此我需要在测试中创建一个新的参数实例,并包含预期的内容。谢谢你的回答,但我非常确定默认情况下使用了eq
matcher,不相同
。我更新了我的问题以澄清它。如您所见,当我放置一份list
副本时,测试将通过(这意味着使用了eq
matcher)。但是这个解决方案不能满足我们。啊,好的。那我有另一个解决办法!很高兴它能起作用;)我使用mockito已经有一段时间了,但这是我第一次这样使用doAnswer()
:p