Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Mockito:若传递给mock的参数被修改了怎么办?_Java_Unit Testing_Pass By Reference_Mockito - Fatal编程技术网

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