8

次のコードがあるとします。

    LinkedList list = mock(LinkedList.class);
    doCallRealMethod().when(list).clear();
    list.clear();

このテストを実行すると、LinkedList#clear の最初の行から NullPointerException がスローされます。

public void clear() {
    Entry<E> e = header.next;
    while (e != header) {
        Entry<E> next = e.next;
        //Code omitted. 

ただし、ヘッダーは以前にインスタンス化されています:

private transient Entry<E> header = new Entry<E>(null, null, null);

誰かがモックの作成中に何が起こっているのか説明してもらえますか?

####### アップデート。######

すべての回答、特に Ajay の回答を読んだ後、Objenesis のソース コードを調べたところ、Reflection API を使用して (CGLIB を介して) プロキシ インスタンスを作成しているため、階層内のすべてのコンストラクターが java.lang.Object までバイパスされていることがわかりました。

この問題をシミュレートするサンプル コードは次のとおりです。

public class ReflectionConstructorTest {

    @Test
    public void testAgain() {

        try {
            //java.lang.Object default constructor
            Constructor javaLangObjectConstructor = Object.class
                    .getConstructor((Class[]) null);
            Constructor mungedConstructor = ReflectionFactory
                    .getReflectionFactory()
                    .newConstructorForSerialization(CustomClient.class, javaLangObjectConstructor);

            mungedConstructor.setAccessible(true);

            //Creates new client instance without calling its constructor
            //Thus "name" is not initialized.
            Object client = mungedConstructor.newInstance((Object[]) null);

            //this will print "CustomClient" 
            System.out.println(client.getClass());
            //this will print "CustomClient: null". name is null.
            System.out.println(client.toString());

        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}


class CustomClient {
    private String name;

    CustomClient() {
        System.out.println(this.getClass().getSimpleName() + " - Constructor");
        this.name = "My Name";
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + ": " + name;
    }
}
4

3 に答える 3

8

あなたはMockitoに本物をクリアで呼び出すように頼んでいるだけです、基礎となるオブジェクトはまだあなたのためにMockitoによって作成された偽物です。実際のLinkedListが必要な場合は、LinkedListを使用してください。BDDの最も熱心な純粋主義者だけが、周囲のすべてをモックするように指示します。つまり、あなたは文字列を嘲笑していませんか?

Mockitoの作者自身は、本物を呼び出すことはほとんど使用されるべきではなく、通常はレガシーコードをテストするためだけに使用されるべきであると述べています。

実際のオブジェクトをスパイする(呼び出しを追跡する)必要がある場合は、Mockitoにもこの機能があります。

List list = new LinkedList();
List spy = spy(list);

スパイを使用すると、必要に応じてメソッドをスタブ化できます。基本的にはモックのように機能しますが、そうではありません;)

于 2013-01-17T08:10:39.577 に答える
6

あなたの推論は完璧です。
重要な問題は、実際のLinkedListオブジェクトを操作していないことです。舞台裏で何が起こっているかは次のとおりです。

Mockito's から提供されるオブジェクトは、CGLIB ライブラリmock()Enhancerオブジェクトです。

私にとっては、次のようなものですjava.util.LinkedList$$EnhancerByMockitoWithCGLIB$$cae81a28

フィールドがデフォルト値に設定されていても、プロキシのように機能します。(ヌル、0 など)

于 2013-01-17T08:40:30.853 に答える
1

クラスをモックする場合、使用しているオブジェクトは偽物であるため、変数はインスタンス化されず、メソッドは期待どおりに機能しません。リフレクションを使用してヘッダーの値を設定できますが、これはお勧めしません。theadam が言ったように、最善の方法はリストを使用することです。

于 2013-01-17T08:13:03.607 に答える