10

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 レコードは のコピーではなくを参照するため、上記のコードでは、Mockito は呼び出し中に実際に渡されたバージョン ( ) ではなく、変更されたバージョン(空のリスト) に対して検証します!listlist[][1,2,3]

質問:

以下のような防御的なコピーを行う以外に、この問題に対するエレガントでクリーンな解決策はありますか (これは実際には役立ちますが、この解決策は好きではありません)。

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

テストで技術的な問題を修正するためだけに、正しい製品コードを変更してそのパフォーマンスを低下させたくはありません。

Mockito でよくある問題と思われるため、ここでこの質問をしています。それとも、何か間違ったことをしただけですか?

PS。これは実際のコードではないので、リストを作成してからクリアする理由などを聞かないでください。実際のコードでは、似たようなことをする必要があります :-)。

4

1 に答える 1

15

ここでの解決策は、カスタマイズされた回答を使用することです。2 つのコード サンプル: 1 つ目は使用されるテスト クラス、2 つ目はテストです。

まず、テストクラス:

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

もちろん、そのようなテストを作成するには、テストが意味のあるものになるように、「外側のオブジェクト」に十分な状態を注入できる必要があります...ここでは比較的簡単です。

于 2013-06-10T15:35:47.073 に答える