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


136

私は特に開発とユニットテストの初心者です。私の要件はかなり単純だと思いますが、私はこれについて他の考えを知りたいと思っています。

私のように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());
}

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

回答:


86

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

コードがこれを行う方法を提供していない場合、TDD(テスト駆動開発)に対して誤って因数分解されます。


4
ありがとう。分かった。多くの内部メソッド、モックが必要になる可能性があるが事前にsetXXX()を介して設定できるとは限らないクラスがある場合に、モックを使用して統合テストを実行するにはどうすればよいのかと思います。
Anand Hemmige、2012年

2
テスト構成で依存関係注入フレームワークを使用します。作成しようとしている統合テストのシーケンス図を作成します。実際に制御できるオブジェクトにシーケンス図を因数分解します。これは、上に示した依存オブジェクトのアンチパターンを持つフレームワーククラスで作業している場合、シーケンス図の観点から、オブジェクトとその不適切に分解されたメンバーを単一のユニットと見なす必要があることを意味します。制御しやすいコードの因数分解を調整して、よりテストしやすくする準備をしてください。
kittylyst 2012年

9
@kittylyst様、おそらくTDDの観点から、またはあらゆる種類の合理的な観点から、それはおそらく間違っています。しかし、開発者がまったく意味のない場所で作業することもあり、その唯一の目標は、割り当てたストーリーを完成させて去ることだけです。はい、それは間違っています、それは意味がありません、資格のない人々が重要な決定とすべてのものを取ります。つまり、結局のところ、アンチパターンは多くの勝利を収めています。
アマナス2015

1
これについて知りたいのですが、クラスメンバーが外部から設定する理由がない場合、テストのためだけにセッターを作成する必要があるのはなぜですか。ここでの「2番目」のクラスは、実際には、テストするオブジェクトの構築中に初期化されるFileSystemマネージャーまたはツールであると想像してください。FirstクラスをテストするためにこのFileSystemマネージャーをモックしたい理由はすべてありますが、アクセス可能にする理由はありません。Pythonでこれを行うことができるので、Mockitoを使ってみませんか?
Zangdar

65

コードを変更できない場合、これは不可能です。しかし、依存関係注入が好きで、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());
   }
}

これはとても簡単です。


2
InjectMocksなので、これは他の答えよりも良い答えだと思います。
sudocoder、2015年

私のようなテスト初心者として、特定のライブラリとフレームワークを信頼する方法は面白いです。私はあなたがそれは私が示されるまで...これは単なる再設計する必要性を示す悪いアイデアだったと仮定している(非常にはっきりときれい)Mockitoで実際に可能。
マイクげっ歯類

9
@Resourceとは?
IgorGanapolsky 2017

3
@IgorGanapolsky @リソースは、Java Springフレームワークによって作成/使用される注釈です。これをSpringに示す方法は、これがSpringによって管理されるBean /オブジェクトであることです。stackoverflow.com/questions/4093504/resource-vs-autowired baeldung.com/spring-annotations-resource-inject-autowire これはmockitoのものではありませんが、非テストクラスで使用されるため、テスト。
Grez.Kev 2018年

この答えがわかりません。あなたはそれが不可能だと言い、それからそれが可能であることを示しますか?ここでは正確に何ができないのですか?
ゴールドネーム

35

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

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

このような:

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

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


とった。私はおそらくあなたの最初の提案に行きます。
Anand Hemmige、2012年

気になるのは、アプリケーションレベルまたはパッケージレベルでオブジェクト/メソッドをモックできる方法またはAPIがあることです。?私が言っていることは、上の例で「Second」オブジェクトをモックしたとき、テストのライフサイクルを通じて使用されるSecondのすべてのインスタンスをオーバーライドできる方法があると思います。?
Anand Hemmige、2012年

@AnandHemmigeは実際には2番目のインスタンス(コンストラクター)が不要です。これにより、不要な「2番目の」インスタンスの作成が回避されます。あなたのクラスはそのようにうまく分離されます。
ソウルチェック2012年

10
Mockitoは、モックをプライベート変数に挿入できるようにするいくつかの素晴らしいアノテーションを提供します。イニシャライザでSecondに@Mock注釈を付け、Firstに注釈を付け、Firstを@InjectMocksインスタンス化します。Mockitoは自動的に、タイプに一致するプライベートフィールドの設定を含め、2番目のモックを最初のインスタンスに挿入する場所を見つけるのが最善です。
jhericks、2012年

@Mock1.5前後でした(おそらく以前は、よくわかりません)。1.8.3 およびとが導入さ@InjectMocksれました。@Spy@Captor
jhericks、2012年

7

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

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

ここで問題は、新しいSecondへのすべての呼び出しが同じモックインスタンスを返すことです。しかし、あなたの単純なケースではこれはうまくいきます。


6

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
}

ここのページで、実際のプロジェクトのコードを変更しました。コンパイルの問題が発生する可能性があります。あなたは一般的な考えを理解していると思います。コードを入手して、必要に応じて自由に使用してください。


実際のユースケースでコードを説明できますか?パブリッククラスのようにtobeMocker(){private ClassObject classObject; ここで、classObjectは置き換えられるオブジェクトと同じです。
Jasper Lankhorst 2017年

あなたの例では、ToBeMocker instance = new ToBeMocker(); およびClassObject someNewInstance = new ClassObject(){@Override //外部依存関係のようなもの}; 次に、TestUtils.refelctSetValue(instance、 "classObject"、someNewInstance); モックのために何をオーバーライドしたいかを理解する必要があることに注意してください。データベースがあり、このオーバーライドが値を返すため、選択する必要がないとします。ごく最近では、実際にメッセージを処理したくはありませんが、メッセージを確実に受信したいサービスバスがありました。したがって、私はこの方法でプライベートバスインスタンスを設定しました。
デイブ2017年

そのコメントにフォーマットがあったと想像する必要があります。削除されました。また、プライベートアクセスがロックされるため、Java 9では機能しません。公式リリースがあり、実際の範囲で作業できるようになったら、他のいくつかの構成要素を使用する必要があります。
デイブ

1

他の多くの人はすでに、コードを再検討してテストしやすくするようにアドバイスしています。優れたアドバイスであり、通常、私が提案するものよりも単純です。

コードを変更してテストしやすくすることができない場合は、PowerMock:https ://code.google.com/p/powermock/

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

別のモックランナーを使用している。また、コンストラクターを呼び出すクラスを準備する必要があります。(これは一般的な問題です-構築されたクラスではなく、コンストラクターを呼び出すクラスを準備してください)

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

次に、テスト設定でwhenNewメソッドを使用して、コンストラクターにモックを返させることができます

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

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でモッキングが実装される方法が原因です。この「モック」サブクラスのインスタンスのみがモック動作を実行できるため、テスト済みのコードで他のインスタンスの代わりにそれらを使用する必要があります。


3
問題は、JMockitがMockitoより優れているかどうかではなく、Mockitoでそれを行う方法です。競争を打ち砕く機会を探すのではなく、より良い製品を作ることに固執しましょう!
TheZuck 2013年

8
オリジナルのポスターには、彼がMockitoを使用しているとしか書かれていません。Jockがこの状況を処理できるというヒントはそれほど不適切ではありません。
ボンベ、2014年

0

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

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

Stack Overflowへようこそ!OPの質問を提供する他の回答があり、それらは何年も前に投稿されました。回答を投稿するときは、特に古い質問に回答したり、他の回答にコメントしたりする場合は、新しいソリューションまたは大幅に優れた説明を追加してください。
help-info.de
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.