2013年6月23日日曜日

Android で mockito を使う : モックのメソッドが呼ばれたときの戻り値を指定する

public class MyClassA { MyClassB mMyClassB; public MyClassA(MyClassB b) { mMyClassB = b; } public void hoge() { if (mMyClassB.getPriority() > 0) { handleHighPriority(); } else { handleLowPriority(); } } public void handleHighPriority() { ... } public void handleLowPriority() { ... } } public class MyClassB { private int mPriority; public int getPriority() { return mPriority; } // 内部の処理で mPriority の値が決まる ... } MyClassA の hoge() が呼ばれたとき、MyClassB の priority に応じて対応するメソッドが呼ばれるかどうかテストします。

失敗するテスト public void testWhenHighPriority() { MyClassA mockMyClassA = mock(MyClassA.class); mockMyClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } と書くと hoge() を呼んだ時点で NullPointerException が発生して handleHighPriority() が呼ばれずテストに失敗します。
mock() で作成した mockMyClassA では mClassB が null になっているからです。

そこで MyClassB をモックにし、それを MyClassA のコンストラクタに与えるようにします。

失敗するテスト public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(myClassA, times(1)).handleHighPriority(); verify(myClassA, never()).handleLowPriority(); } これで hoge() を呼んだ時点で NullPointerException が発生することは無くなりましたが、 org.mockito.exceptions.misusing.NotAMockException: Argument passed to verify() is of type MyClassA and is not a mock! が起こってテストに失敗します。 verify() に渡すインスタンスはモック化されていないといけないからです。 実際のインスタンスをモック化するには Mockito.spy() を使います。

失敗するテスト public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); MyClassA mockMyClassA = spy(new MyClassA(mockMyClassB)); mockMyClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } これで NotAMockException が発生することは無くなりましたが、MyClassB の mPriority は初期値 = 0 のままなので、priority が 0 より大きいときのテストができません。

そこで mockito の機能を使います。
mockito では、「モックのあるメソッドが呼ばれたときにこの値を返す」という指定ができます。

"あるメソッドが呼ばれた" ということを指定するのが Mockito.when() メソッドです。
"そのときにこの値を返す" ということを指定するのが thenReturn() メソッドです。

MyClassB の getPriority() が呼ばれたときに 100 を返して欲しいなら when(mockMyClassB.getPriority()).thenReturn(100); のように書きます。 /** * MyClassA.hoge() を呼んだとき、MyClassB の priority が 100 なら * MyClassA.handleHighPriority() が呼ばれることを確認する */ public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); when(mockMyClassB.getPriority()).thenReturn(100); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } /** * MyClassA.hoge() を呼んだとき、MyClassB の priority が 0 なら * MyClassA.handleLowPriority() が呼ばれることを確認する */ public void testWhenLowPriority() { MyClassB mockMyClassB = mock(MyClassB.class); when(mockMyClassB.getPriority()).thenReturn(0); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(mockMyClassA, times(1)).handleLowPriority(); verify(mockMyClassA, never()).handleHighPriority(); } thenReturn(Integer value, Integer... values) のように第2引数が可変長引数になっているものも用意されています。
thenReturn(10, 20, 30, 100) のように指定すると、対応するメソッドが呼ばれるごとに返される値が後の引数になり、引数よりも呼ばれる回数が多くなったら一番最後の引数の値が返されます。



1 件のコメント:

  1. 初めまして。

    モックライブラリ、凄く便利ですよね。

    返信削除