【问题标题】:Mockito: what if argument passed to mock is modified?Mockito:如果传递给 mock 的参数被修改了怎么办?
【发布时间】:2013-06-10 15:24:12
【问题描述】:

我们在 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 记录 referencelist 而不是 list 的副本,因此在上面的代码中,Mockito 验证 修改版本(空列表,[])而不是调用期间实际传递的那个 ([1,2,3])!

问题:

除了像下面那样做一个防御性副本(这实际上有帮助,但我们不喜欢这个解决方案)之外,还有什么优雅而干净的解决方案吗?

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

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

我在这里问这个问题是因为这似乎是 Mockito 的常见问题。还是我们做错了什么?

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

【问题讨论】:

  • 您是否真的在同一参数类的不同实例上进行验证?
  • @fge 是的,我无法访问测试代码中的原始实例,因此我需要在测试中创建一个具有预期内容的新参数实例。

标签: java unit-testing pass-by-reference mockito


【解决方案1】:

这里的解决方案是使用自定义答案。两个代码示例:第一个是使用的测试类,第二个是测试。

一、测试类:

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"));
}

当然,能够编写这样的测试需要你能够注入足够的状态到你的“外部对象”中,这样​​测试才有意义……这里就比较简单了。

【讨论】:

  • 感谢您的回答,但我很确定默认情况下使用 eq 匹配器,而不是 same。我更新了我的问题以澄清它。如您所见,当我放置list 的副本时,测试将通过(这意味着使用了eq 匹配器)。但是这个解决方案并不能让我们满意。
  • 嗯,好的。那我还有一个解决办法!
  • 很高兴它有效 ;) 我使用 mockito 已经有一段时间了,但这是我第一次以这种方式使用 doAnswer() :p
猜你喜欢
  • 2011-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
相关资源
最近更新 更多