xUTP Magazine - 0002/Mockito Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

:書いた人:大中浩行(せとあずさ♂)([[@setoazusa|https://twitter.com/setoazusa]])

!MockitoでサクサクTest Double生活(下)

前回に引き続き、Mockitoの紹介を行います。今回はMockitoの特徴のSpy機能と、7月にRC1がリリースされたMockito1.9.0の新機能の紹介です。

!!前号の訂正
[[前回の記事|0001/Mockito]]で、

""メソッドの呼び出し回数を検証する場合は、timesメソッドを使用します。

と書きましたが、引数を1つ取る[[verify|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#verify(T)]]メソッドは第2引数に[[times|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#times(int)]](1)を指定する検証のエイリアスですので、「メソッドが1回呼ばれた」ことを検証する場合はtimes(1)を指定する必要はありません。

なので、前回で例示した
verify(response, times(1)).sendRedirect("/top.jsp");
は、
verify(response).sendRedirect("/top.jsp");
で用を足します。

!!Spy

Mockitoの特徴の一つとして、実オブジェクトに部分的にスタブの適用を行う、Spy機能があります。

Spyを作成するときは[[spy|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#spy(T)]]メソッドを使用します。Mockと同じく、finalクラスのモックを作成することはできません。

モックオブジェクトにスタブを適用する際は、

when(list.add("value")).thenReturn(true);

のような形式を使用しますが、Spyの場合はこの方法で記述すると、実オブジェクトのメソッドが呼ばれるため、オブジェクトの状態によっては例外が発生します。

List<String> list = new ArrayList<String>();
list = spy(list);
//IndxOutOfBoundsExceptionになる
when(list.get(0)).thenReturn("value");

これを避けるには、[[doReturn|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/Stubber.html#doReturn(java.lang.Object)]]メソッドや[[doThrow|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/Stubber.html#doThrow(java.lang.Throwable)]]メソッドを使用します。

List<String> list = new ArrayList<String>();
list = spy(list);
// これは大丈夫
doReturn("value").when(list).get(0);
assertThat(list.get(0), is("value"));

この点だけ注意すれば、モックと同じような感覚でSpyを使用することが出来るでしょう。

!!Mockito1.9の新機能
!!!アノテーションによるSpy生成
Mockitoにはテストケースのインスタンス変数に[[@Mock|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mock.html]]アノテーションをつけることにより、モックの生成を行う機能があります。

Mockitoの1.9.0より、[[@Spy|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Spy.html]]アノテーションが導入され、インスタンス変数に[[@Spy|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Spy.html]]アノテーションをつけることにより、Spyの生成が出来るようになりました。
//MockitoJUnitRunnerを指定する
@RunWith(MockitoJUnitRunner.class)
public class NewFeatureTest {
        /**
         * Spyを生成する
         */
        @Spy
        List<String> list = new ArrayList<String>();
        /**
         * Mockを生成する
         */
        @Mock
        List<String> mockList

!!!ワンライナースタブ
モックオブジェクトの生成とスタブの適用がワンライナーで出来るようになりました。

whenメソッドの引数内でmockメソッドを適用したモックを渡した後、スタブの適用を行い、最後に[[getMock|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/OngoingStubbing.html#getMock()]]メソッドで生成したモックを返します。

@Test
public void ワンライナーによるモック生成() throws Exception {
     HttpServletRequest request = when(mock(HttpServletRequest.class).getParameter("param")).thenReturn("value").getMock();
     assertThat(request.getParameter("param"), is("value"));
}

!!!検証時のスタブメソッド除外
1つのモックオブジェクトにスタブの適用と検証を行った場合、スタブを適用したメソッドの呼び出しもモックオブジェクトとの間のインタラクションに含まれてしまうため、[[verifyNoMoreInteractions|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#verifyNoMoreInteractions(java.lang.Object...)]]メソッドなどを使用した場合に例外となっていました。

Mockitoの1.9.0より、[[ignoreStubs|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#ignoreStubs(java.lang.Object...)]]メソッドを使うことによって、スタブメソッドの呼び出しは検証の対象から除外することが出来るようになりました。

@Test
public void ignoreStubによる検証の除外() throws Exception {
List<Integer> mock1 = mock(List.class);

//スタブの適用
when(mock1.get(0)).thenReturn(10);

//スタブメソッドの呼び出しもインタラクションに含まれる
mock1.get(0);

mock1.clear();

verify(mock1).clear();

try{
verifyNoMoreInteractions(mock1);
fail();
} catch(NoInteractionsWanted ignore){
//検証時に例外になっていた
}
//こちらは例外にならない
verifyNoMoreInteractions(ignoreStubs(mock1));

}

ですが、著者の見解としては、この機能が必要になるということは、SUT(System Under Test){{fn ("テストケースがテスト対象とするコンポーネント")}} とモックの間で間接的入力と間接的出力の両方が必要になっているということであり、まずインターフェースの再検討を考えたほうがよいのではないか、と考えています。(コードベースが既存の場合はこの限りではありません)

!!!さいごに
駆け足ですがMockitoについて紹介してきました。MockitoのAPIはここまで見て分かるとおり、非常にわかりやすく出来ていますので、テストのリズムを妨げることなく、モックの生成ができると思います。皆さんもぜひお試しください。