web-dev-qa-db-ja.com

Mockitoを使用してクラスのメンバー変数をモックする

私は特に開発と単体テストの初心者です。私の要件は非常に簡単だと思いますが、他の人の考えを知りたいと思っています。

次のような2つのクラスがあるとします-

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }
}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

First.doSecond()メソッドをテストする単体テストを書いているとしましょう。ただし、Second.doSecond()クラスをそのようにモックしたいとします。私はこれを行うためにMockitoを使用しています。

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

モックが有効にならず、アサーションが失敗することがわかりました。私がテストしたいクラスのメンバー変数をモックする方法はありません。 ?

116
Anand Hemmige

モックを渡すことができるように、メンバー変数にアクセスする方法を提供する必要があります(最も一般的な方法は、セッターメソッドまたはパラメーターを取るコンストラクターです)。

コードがこれを行う方法を提供していない場合、TDD(テスト駆動開発)に対して誤ってファクタリングされます。

76
kittylyst

コードを変更できない場合、これは不可能です。しかし、私は依存性注入が好きで、Mockitoはそれをサポートしています:

public class First {    
    @Resource
    Second second;

    public First() {
        second = new Second();
    }

    public String doSecond() {
        return second.doSecond();
    }
}

あなたのテスト:

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
   @Mock
   Second second;

   @InjectMocks
   First first = new First();

   public void testFirst(){
      when(second.doSecond()).thenReturn("Stubbed Second");
      assertEquals("Stubbed Second", first.doSecond());
   }
}

これはとても素敵で簡単です。

53
Janning

コードをよく見ると、テストのsecondプロパティはまだモックではなくSecondのインスタンスであることがわかります(コード内でモックをfirstに渡さないでください)。

最も簡単な方法は、secondクラスでFirstのセッターを作成し、モックを明示的に渡すことです。

このような:

public class First {

Second second ;

public First(){
    second = new Second();
}

public String doSecond(){
    return second.doSecond();
}

    public void setSecond(Second second) {
    this.second = second;
    }


}

class Second {

public String doSecond(){
    return "Do Something";
}
}

....

public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");


First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}

もう1つは、SecondのコンストラクターパラメーターとしてFirstインスタンスを渡すことです。

コードを変更できない場合、唯一のオプションはリフレクションを使用することです。

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");


    First first = new First();
    Field privateField = PrivateObject.class.
        getDeclaredField("second");

    privateField.setAccessible(true);

    privateField.set(first, sec);

    assertEquals("Stubbed Second", first.doSecond());
}

しかし、おそらくあなたは、あなたが制御していないコードでテストを行うことはまれなので、おそらく可能です(ただし、外部ライブラリをテストする必要があるシナリオは想像できますが、その作者はそうしませんでした:))

31
soulcheck

メンバー変数を変更できない場合、これを回避する別の方法はpowerMockitを使用して呼び出すことです

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);

ここでの問題は、new Secondを呼び出すと、同じ模擬インスタンスが返されることです。しかし、あなたの単純なケースでは、これは機能します。

6
user1509463

Mockitoがスーパーコンストラクターを呼び出さないため、プライベート値が設定されていないという同じ問題がありました。反射でモックを強化する方法は次のとおりです。

最初に、これらのリフレクションメソッドを含む多くの有用なユーティリティを含むTestUtilsクラスを作成しました。リフレクションアクセスは、毎回実装するのが少し不安定です。私はこれらのメソッドを作成して、何らかの理由でモックパッケージが含まれていないプロジェクトのコードをテストしました。

public class TestUtils {
    // get a static class value
    public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(classToReflect, fieldNameValueToFetch);
            reflectField.setAccessible(true);
            Object reflectValue = reflectField.get(classToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // get an instance value
    public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
            Object reflectValue = reflectField.get(objToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // find a field in the class tree
    public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField = null;
            Class<?> classForReflect = classToReflect;
            do {
                try {
                    reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
                } catch (NoSuchFieldException e) {
                    classForReflect = classForReflect.getSuperclass();
                }
            } while (reflectField==null || classForReflect==null);
            reflectField.setAccessible(true);
            return reflectField;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
        }
        return null;
    }
    // set a value with no setter
    public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameToSet);
            reflectField.set(objToReflect, valueToSet);
        } catch (Exception e) {
            fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
        }
    }

}

次に、このようなプライベート変数を使用してクラスをテストできます。これは、コントロールも持たないクラスツリーの奥深くをモックするのに便利です。

@Test
public void testWithRectiveMock() throws Exception {
    // mock the base class using Mockito
    ClassToMock mock = Mockito.mock(ClassToMock.class);
    TestUtils.refectSetValue(mock, "privateVariable", "newValue");
    // and this does not prevent normal mocking
    Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
    // ... then do your asserts
}

このページの実際のプロジェクトのコードを変更しました。コンパイルの問題が1つまたは2つある可能性があります。あなたは一般的なアイデアを得ると思います。コードを入手して、便利であるとわかったら使用してください。

6
dave

他の多くの人は、コードをよりテストしやすくするためにコードを再考することを既に勧めています-良いアドバイスであり、通常私が提案しようとしているものよりも簡単です。

テストしやすくするためにコードを変更できない場合、PowerMock: https://code.google.com/p/powermock/

PowerMockはMockitoを拡張し(したがって、新しいモックフレームワークを学ぶ必要はありません)、追加の機能を提供します。これには、コンストラクターがモックを返す機能が含まれます。強力ですが、少し複雑です-慎重に使用してください。

別のMockランナーを使用します。そして、コンストラクターを呼び出すクラスを準備する必要があります。 (これはよくある落とし穴です-構築されたクラスではなく、コンストラクターを呼び出すクラスを準備してください)

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})

次に、テストのセットアップでwhenNewメソッドを使用して、コンストラクターがモックを返すようにします

whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));
1
jwepurchase

MockitoのSpringのReflectionTestUtilsの代替が必要な場合は、

Whitebox.setInternalState(first, "second", sec);
0

はい、次のテストが示すように、これを行うことができます(私が開発したJMockitモックAPIで作成されています)。

@Test
public void testFirst(@Mocked final Second sec) {
    new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

ただし、Mockitoを使用すると、このようなテストを記述できません。これは、モックがモックされるクラスのサブクラスが作成されるMockitoで実装されている方法によるものです。この「モック」サブクラスのインスタンスのみがモックされた動作を持つことができるため、テストされたコードで他のインスタンスの代わりにそれらを使用する必要があります。

0
Rogério