読者です 読者をやめる 読者になる 読者になる

心地良すぎるモックライブラリ Mockito 〜その2〜


心地良すぎるモックライブラリ Mockito 〜その1〜 - A Memorandumの続き。

呼び出し順序の妥当性検証

以下のように、2つのモックがあり、それぞれのモックの add() メソッドの呼び出し順序を検証したい場合、

List firstMock = mock(List.class);
List secondMock = mock(List.class);
 
firstMock.add("was called first");
secondMock.add("was called second");

InOrder を使用します。

InOrder inOrder = inOrder(firstMock, secondMock);
 
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

firstMock の add() メソッドが secondMock の add() メソッドより前に呼び出されていることが検証できます。
順序の検証は、全てのモックを指定する必要はなく、順序を確認したいモックに対してのみ InOrder を作成することが可能です。

余計なメソッド呼び出しが行われていないことを検証する

verifyNoMoreInteractions を使用することで、想定するメソッド呼び出しのみ行われていることを検証できます。

mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");

verifyNoMoreInteractions(mockedList);

リファクタリングによるテストコードへの影響を抑えるため、verifyNoMoreInteractions の使用は最低限にするべきです。never() はこの有効な代替手段となるでしょう。

Mockito のアノテーション

@Mock アノテーションを使用することで、テストコードを簡素に記述することができます。

public class ArticleManagerTest { 
    @Mock private ArticleCalculator calculator;
    ・・・


アノテーションの初期化のために以下のコードを呼び出す必要があります。

MockitoAnnotations.initMocks(testClass);


JUnit用のテストランナーを使用する場合は、上記コードの呼び出しは不要になります。

@RunWith(MockitoJUnit44Runner.class)
public class ArticleManagerTest { 

その他、@Spy, @Captor, @InjectMocks といったアノテーションが用意されています。

複数回のモックメソッド呼び出しの結果を変化させる

モックメソッドの呼び出しを複数回行い、それぞれで異なる戻り値を定義するには以下のようにします。

when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

1回目の呼び出しでは RuntimeException がスローされ、2回目の呼び出しでは "foo" が返却されます。
それ以降の呼び出しでは、最後に定義した "foo" が返却されます。


以下のような簡素な記述方法も提供されています。

when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");

コールバック付きの戻り値定義

モックメソッド呼び出しの戻り値に Answer を使用することで戻り値の内容を柔軟に定義できます。

when(mock.someMethod(anyString())).thenAnswer(new Answer() {
    Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        Object mock = invocation.getMock();
        return "called with arguments: " + args;
    }
});


上記のように定義されたメソッドを

System.out.println(mock.someMethod("foo"));

のように呼びだすと、"called with arguments: foo" の出力が得られます。


通常は thenReturn() や thenThrow() のみを使用して、簡素なテストコードとすべきであり、Answer の利用は通常控えるべきです。

voidメソッドの振舞を定義するdoXXファミリー

void メソッドはコンパイラによる扱いが異なるため、when(Object) とは異なるアプローチが用意されています。doXXファミリーは doThrow(Throwable), doAnswer(Answer), doNothing(), doReturn(Object) があります。

void メソッドから例外をスローさせるには、前述の通り以下のようにします。

doThrow(new RuntimeException()).when(mockedList).clear();

doAnswer はvoidメソッドをジェネリックに使用したい場合に以下のように使います。

doAnswer(new Answer() {
    public Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        Mock mock = invocation.getMock();
        return null;
    }})
.when(mock).someMethod();


doNothing はvoidメソッドの呼び出し順序により振舞を変えたい場合に以下のように使用できます。

doNothing().
doThrow(new RuntimeException())
   .when(mock).someVoidMethod();

1回目はなにもせず、2回目の呼び出しでRuntimeExceptionをスローします。
また、後述のspyを利用する場合に、対象メソッドの呼び出しにて動作を規定しない場合にも使用できます。

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

doNothing().when(spy).clear();


doReturn は when(Object) が使用できない以下のような場面で使用します。

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

// when(spy.get(0)).thenReturn("foo"); は使用できない
doReturn("foo").when(spy).get(0);

RuntimeException をスローするように定義済みのモックの動作を上書くにも doReturn を使用する必要があります。

when(mock.foo()).thenThrow(new RuntimeException());

// when(mock.foo()).thenReturn("bar"); は使用できない
doReturn("bar").when(mock).foo();

実オブジェクトの動作を変えるspy

実オブジェクトのメソッドの内、必要とするメソッドだけ動作を変えるには spy を利用します。動作を定義していないメソッドについては実際のオブジェクトのメソッドが使用されます。

List list = new LinkedList();
List spy = spy(list);
 
when(spy.size()).thenReturn(100);

実際のオブジェクトを spy() に渡すだけです。

spy.add("one"); // 実際のオブジェクトのメソッド呼び出し
spy.size();     // spy にて定義された戻り値 100 が返却される


spy オブジェクトに対して verify を呼び出すこともできます。

verify(spy).add("one");

spy オブジェクトに対してスタブメソッドを定義する場合、when(Object) が利用できない場合があります。例えば以下の例では doReturn を使う必要があります(when を利用した場合、実際のオブジェクトのget(0)が呼び出され、この時点でlistが空の場合に IndexOutOfBoundsException が投げられてしまいます)。

// when(spy.get(0)).thenReturn("foo"); この定義は無効
doReturn("foo").when(spy).get(0);


なお、finalメソッドの場合にはトラブルの元になるため使用を控えるべきです。
1.8.0 からはモックに部分的に実際のメソッド呼び出しを行う thenCallRealMethod が追加されています(後述)。


次回に続く。。





Test-Driven Development with Mockito

Test-Driven Development with Mockito

Mockito Cookbook

Mockito Cookbook