web-dev-qa-db-ja.com

Mockitoを使用してテスト用のメソッドをスタブおよび実行する

最近、jUnitとMockito向けの質問をいくつか聞いたことがありますが、それを理解するのに苦労しています。チュートリアルはすべて非常に単純な例であるため、テストケースをスケールアップしてクラスで機能するように努力しています。

現在、webappのエージェントの1つにあるメソッドのテストケースをいくつか作成しようとしています。このメソッドは、エージェント内の他のいくつかのメソッドと対話して、いくつかのオブジェクトを検証します。この1つの方法を今すぐテストしたいだけです。

これが私がやろうとしたことです:

  1. 次のように、エージェントのMockitoオブジェクトを作成します。

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. Mockito.whenを使用してスタブ(できれば正しい用語)をセットアップします。

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. 次のように私のメソッドを実行してみてください:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

myReturnValueにあるものを期待していましたが、代わりに0を受け取ったので、デバッグを試みました。メソッドを呼び出すと、実行されません。メソッドの最初の行には、決して触れられないデバッグポイントがあります。

クラスの1つのメソッドでコードを実行したいが、クラス内の他のメソッド(外部のデータベースと対話しようとするメソッド)に偽の値を強制的に返したい場合。これはMockitoで可能ですか?

私の現在のアプローチ方法は正しいテストスタイルではないように見えますが、どのように前進するのか分かりません。クラスのモックを作成し、1つのメソッドを通常のように実行し、他のメソッドをスタブして特定の値を返すようにして、この1つのメソッドのテスト中にデータアクセスを処理する必要はありませんか?

45
Kyle

MockSpyを混同しています。

モックではすべてのメソッドはスタブ化され、「スマートリターンタイプ」を返します。これは、動作を指定しない限り、モックされたクラスのメソッドを呼び出しても何もしないことを意味します。

スパイでは、クラスの元の機能はそのままですが、スパイでのメソッド呼び出しを検証し、メソッドの動作をオーバーライドすることもできます。

あなたが欲しいのは

MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);

簡単な例:

static class TestClass {

    public String getThing() {
        return "Thing";
    }

    public String getOtherThing() {
        return getThing();
    }
}

public static void main(String[] args) {
    final TestClass testClass = Mockito.spy(new TestClass());
    Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
    System.out.println(testClass.getOtherThing());
}

出力は次のとおりです。

Some Other thing

NB:テストするクラスの依存関係を実際にモックする必要がありますnotクラス自体。

70

したがって、テスト対象のクラスをモックするという考えは、テストの実践にとって無意味です。これを行うべきではありません。そのようにしたので、テストはテスト対象のクラスではなく、Mockitoのモッククラスに入ります。

スパイは、スパイクラスの周りのラッパー/プロキシのみを提供するため、機能しません。実行がクラス内に入ると、プロキシを通過しないため、スパイにヒットしません。更新:これはSpringプロキシには当てはまると思いますが、Mockitoスパイには当てはまらないようです。メソッドm1()m2()を呼び出すシナリオを設定しました。オブジェクトとスタブm2()doNothingにスパイします。テストでm1()を呼び出すと、クラスのm2()に到達しません。 Mockitoはスタブを呼び出します。したがって、求められていることを達成するためにスパイを使用することは可能です。しかし、私はそれを悪い慣行(IMHO)と見なすことを繰り返します。

テスト対象のクラスが依存するすべてのクラスをモックする必要があります。これにより、テスト対象のメソッドが呼び出すクラスを制御するという点で、テスト中のメソッドによって呼び出されるメソッドの動作を制御できます。

クラスで他のクラスのインスタンスを作成する場合は、ファクトリーの使用を検討してください。

6
John B

あなたはほとんどそれを持っています。問題は、 テスト中のクラスCUT)が単体テスト用に構築されていないことです。 really been TDD 'd。

このように考えてください…

  • クラスの関数をテストする必要があります-それを呼び出しましょうmyFunction
  • その関数は、別のクラス/サービス/データベースの関数を呼び出します
  • その関数は、CUTで別のメソッドも呼び出します

単体テストで

  • 具体的なカットまたは@Spyを作成する必要があります
  • @Mock他のすべてのクラス/サービス/データベース(つまり、外部依存関係)
  • couldCUTで呼び出される他の関数をスタブしますが、実際にはそうではありませんnit =テストを行う必要があります

厳密にテストしていないコードの実行を避けるために、そのコードを@Mockedにできるものに抽象化することができます。

このveryの簡単な例では、オブジェクトを作成する関数をテストするのは難しいでしょう

public void doSomethingCool(String foo) {
    MyObject obj = new MyObject(foo);

    // can't do much with obj in a unit test unless it is returned
}

ただし、サービスを使用してMyObjectを取得する関数はテストが容易です。これは、テストが困難/不可能なコードをこのメソッドをテスト可能にするものに抽象化したためです。

public void doSomethingCool(String foo) {
    MyObject obj = MyObjectService.getMeAnObject(foo);
}

myObjectServiceをモックして、.getMeAnObject()がfoo変数で呼び出されることも確認できるためです。

4
andyb

簡潔な答え

あなたの場合の方法:

int argument = 5; // example with int but could be another type
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));

長い答え

実際には、少なくともJava 8で何をしたいのかは可能です。たぶん、この質問とこの質問を許可するJava 8を使用しているので、他の人からこの答えが得られなかったかもしれませんJava 8のリリース前です(値だけでなく、他の関数に関数を渡すことができます)。

データベースクエリへの呼び出しをシミュレートしましょう。このクエリは、FreeRoms = XおよびStarNumber = Yを持つHotelTableのすべての行を返します。テスト中に期待するのは、このクエリが異なるホテルのリストを返すことです。返されたホテルはすべて同じ値XおよびYを持ちます。他の値と私は自分のニーズに応じてそれらを決定します。次の例は単純ですが、もちろんより複雑にすることもできます。

そのため、異なる結果を返す関数を作成しますが、それらはすべてFreeRoms = XおよびStarNumber = Yです。

static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
    ArrayList<Hotel> HotelArrayList = new ArrayList<>();
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));

    return HotelArrayList;
}

たぶんSpyの方が良いかもしれませんが(試してみてください)、私はこれを模擬クラスで行いました。ここで私はどのように行います(anyInt()値に注意してください):

//somewhere at the beginning of your file with tests...
@Mock
private DatabaseManager mockedDatabaseManager;

//in the same file, somewhere in a test...
int availableRoomNumber = 3;
int starNumber = 4;
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));
1
fresko