Mockito:如果传递给mock的参数被修改了怎么办?

Pio*_*zyk 8 java unit-testing pass-by-reference mockito

我们遇到了与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));
   }
}
Run Code Online (Sandbox Code Playgroud)

这会导致错误消息,如:

Arguments are different! Wanted: 
foo.bar([1,2,3]); 
Actual invocation has different arguments:
foo.bar([]);
Run Code Online (Sandbox Code Playgroud)

刚刚发生了什么:

记录的Mockito 参考来list,而不是副本list,所以在上面的的Mockito验证码对修改后的版本(空单,[])而不是调用过程中实际传递的一个([1,2,3])!

题:

除了做一个像下面这样的防御性副本之外,对这个问题有没有优雅和干净的解决方案(实际上有帮助,但我们不喜欢这个解决方案)?

   public void fun(){
        list = Arrays.asList(1,2,3);
        foo.bar(new ArrayList(list));
        list.clear();
    }
Run Code Online (Sandbox Code Playgroud)

我们不希望修改正确的生产代码并降低其性能,只是为了解决测试中的技术问题.

我在这里问这个问题,因为它似乎可能是Mockito的常见问题.或者我们只是做错了什么?

PS.这不是一个真正的代码,所以请不要问为什么我们创建一个列表然后清除它等.在实际代码中我们真的需要做类似的事情:-).

fge*_*fge 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在测试中:

@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"));
}
Run Code Online (Sandbox Code Playgroud)

当然,能够编写这样的测试需要你能够注入你的"外部对象"足够的状态,以便测试是有意义的...这里相对容易.

  • 很高兴它有效;) 我已经使用 mockito 很长一段时间了,但这是我第一次以这种方式使用 `doAnswer()` :p (2认同)