xUTP Magazine - 0001/Mockito Diff

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

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

Unit Testを行う場合において、テストケース側から制御することが難しいコンポーネントが行う処理のことを間接的入出力(Indirect Input)といいます。例えばJavaでサーブレットを書く時には、HttpServletクラスの

public void service(ServletRequest req, ServletResponse res)

というメソッドをオーバーライドするわけですが、HttpServetRequestやHttpServletResponseのインスタンスはどこで生成されているのかといえば、それはWebコンテナの内部であって、我々が関与できるところではありません。他にもデータベースであるとか、外部環境とのエクササイズを伴うとか、異常系の動作を確認する必要があるとか、Unit Testを書く上で妨げになるものはたくさんあります。

このような状況で間接的入出力を制御するために、本物のオブジェクトに成り代わって動作するオブジェクトをTest Doubleといいます。Test Doubleを実現するフレームワークには[[easymock|http://easymock.org/]],[[jmock|http://www.jmock.org/]]や[[PowerMock|http://code.google.com/p/powermock/]]などがありますが、APIの直感的なわかりやすさなどで最近注目を集めているのがMockitoです。

!mockitoの環境構築
mockitoは http://code.google.com/p/mockito/ から入手することができます。
依存ライブラリも含まれているmockito-all-(バージョン),.jarをダウンロードするのがよいでしょう。

maven2のセントラルレポジトリからも取得することができます。この場合は、pom.xmlに
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>  
      <version>1.9.0-rc1</version>
      <scope>test</scope>
    </dependency>
と書くことで取得することができます。

Mockitoはorg.mockito.Mockitoとorg.mockito.Matchetsの各クラスをstaticインポートすることによってコーディングします。

import static org.mockito.Mockito.*;
import static org.mockito.Matchers.*;

を記述するだけでも要は足りますが、コンテントアシストの設定をしておくとよりstatic importを補完してくれるのでより快適になります。EclipseのSave in Actionでインポート文を編成している場合や、コーディング規約でimport文のワイルドカードを使用しない場合になっている場合もこの設定が必要になります。

EclipseのPreferences→Java→Editor→Content Assistを選択します。 「New Type」のボタンを押して、org.mockito.Mockito と org.mockito.Matchersを追加します。
http://devtesting.jp/pekema/eclipse_fav.png

!モックの作成
モックを作成するときは [[mock|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#mock(java.lang.Class)]] メソッドを使用します。インターフェースや抽象クラスにも適用可能ですが、finalクラスのモックを作成することはできません。筆者はjava.net.URLクラスをモック化しようとして、この制限にひっかかることがよくあります。
HttpServletRequest request = mock(HttpServletRequest.class);

なお、用語の定義として注意すべきなのは、mockitoのMock Objectは、間接的入出力の設定と間接的入出力の検証の両方が可能ですので、[[xUnit Test Patterns|http://www.amazon.co.jp/dp/0131495054]](xutp)の分類ではTest Spy[[Test Spy|http://xunitpatterns.com/Test%20Spy.html]]に相当するということです。

モックオブジェクトにスタブメソッドを適用するには、[[when|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#when(T)]]メソッドと[[thenReturn|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/OngoingStubbing.html#thenReturn(T)]]メソッドを使用します。

when(request.getServletPath()).thenReturn("/index.jsp");

なお、スタブを適用していないメソッドを呼び出した場合は、nullないしプリミティブ値の初期値が返ります。

スタブメソッドで例外を発生したい場合は、[[thenThrow|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/OngoingStubbing.html#thenThrow(java.lang.Throwable...)]]メソッドを使用します。
PreparedStatement pstmt = mock(PreparedStatement.class);
when(pstmt.executeQuery()).thenThrow(new SQLException("error!"));

なお、返り値がvoidなメソッドに例外をスローする必要があるときは、whenメソッドを使用できないので、[[doThrow|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#doThrow(java.lang.Throwable)]]メソッドを使用する必要があります。
Connection con = mock(Connection.class);
doThrow(new SQLException()).when(con).close();

スタブメソッドにロジックを含ませることができます。[[org.mockito.stubbing.Answer|http://docs.mockito.googlecode.com/hg/latest/index.html?org/mockito/stubbing/Answer.html]] を実装したクラスを作成します。generic型はスタブを適用したいメソッドの返り値を指定します。

private static class EncodeAnswer implements Answer<String>{
     public String answer(InvocationOnMock invocation) throws Throwable {
         return (String) invocation.getArguments()[0] + ";jsessionid=111";
     }
}

メソッド引数は、[[InvocationOnMock|http://docs.mockito.googlecode.com/hg/latest/org/mockito/invocation/InvocationOnMock.html]] から取得することができます。
テストメソッド内では、[[thenReturn|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/OngoingStubbing.html#thenReturn(T)]]の代わりに[[thenAnswer|http://docs.mockito.googlecode.com/hg/latest/org/mockito/stubbing/OngoingStubbing.html#thenAnswer(org.mockito.stubbing.Answer)]]を使用します。

HttpServletResponse response = mock(HttpServletResponse.class);
when(response.encodeRedirectURL(anyString())).thenAnswer(new EncodeAnswer());

ここで[[anyString|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Matchers.html#anyString()]] というメソッドが出てきましたが、これは [[org.hamcrest.Matchers|http://docs.mockito.googlecode.com/hg/latest/index.html?org/mockito/Matchers.html]]に定義されている「どの文字列でも可能」という条件を示すhamcrestのMatcherです。Mockitoは、「○○という引数が渡された時」という引数の条件(適合性)の検証を行う時に、オブジェクトの等価性(equalsメソッド)を使って引数の適合性を検証しますが、org.mockito.MatchersのMatcherを使って条件を定義することもできます。

なお注意点として、引数にMatchersを使う場合は、全ての引数をMatchersにする必要があります。
verify(mock).someMethod(anyInt(), anyString(), "third argument");
のようなやり方はできないので、
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
とする必要があります。

また、引数でorg.hamcrest.Matchersで用意されているラッパークラスやコレクション以外のオブジェクトを条件に指定する場合は、[[any|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Matchers.html#any()]]メソッドを使用しますが、この場合は引数の型にキャストする必要があります。
Service service = mock(Service.class);
when(service.execute((Bean)any())).thenReturn(1);

!モックの検証
メソッドの呼び出しを検証する場合には[[verify|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#verify(T)]]メソッドを使用します。メソッドが「呼ばれていないこと」を検証する場合には、第2引数で[[never|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#never()]]メソッドを呼び出します。

verify(response).sendRedirect("/top.jsp");
verify(request,never()).getRequestDispatcher(anyString());

メソッドの呼び出し回数を検証する場合は、[[times|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#times(int)]]メソッドを使用します。
verify(response, times(1)).sendRedirect("/top.jsp");

なお、[[only|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#only()]] というメソッドがありますが、このメソッドは、テスト対象とスタブの間で、「他のメソッドも含めてこのメソッドしか呼ばれていない」という条件であり、times(1)と同じ条件ではないことに注意してください。

!最後に
以上で、mockitoの基本的な使い方はカバーできたと思います。また、mockitoの[[JavaDoc|http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html]]は非常に充実しているので、細部についてはそちらを参照されることをおすすめします。

次回はMockito1.9の新機能と、Mockitoの特徴の一つspy機能についてとりあげます。