概要
まずはMockitoの使用のチュートリアルを挟み、
後半では筆者の作成した記事「様々なテストダブル」において紹介した
モックオブジェクトをMockitoを使用して実装してみる。
Mockitoの使い方
事前準備
プロジェクトサイトからjarファイルをダウンロードする。
mockito-all-X.X.X.jarをビルドパスに追加する。
実装のイロハ
以下のコードを参考に、モックオブジェクトの基本的な操作を理解する。
public class UnfinishedLogic {
int x;
boolean flag;
String str;
BigDecimal decimal;
public String doSomething() {
return "未実装";
}
}
@Test
public void モックオブジェクトのイロハ() {
// 1. モックオブジェクトの作成
UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
// 2. スタブの戻り値
assertThat(mock.x, is(0));
assertThat(mock.flag, is(false));
assertThat(mock.str, is(nullValue()));
assertThat(mock.decimal, is(nullValue()));
assertThat(mock.doSomething(), is(nullValue()));
// 3. スタブメソッドの定義
when(mock.doSomething()).thenReturn("想定値");
assertThat(mock.doSomething(), is("想定値"));
}
モックオブジェクトの作成
モックオブジェクトを作成するときは、org.mokito.Mokitoクラスのmockメソッドに
モック化したいクラスの型オブジェクトを指定する。
渡すクラスにfinalクラスを指定すると、実行時に例外が発生するので注意。
なお、Mockitoにおいてモックとスタブに実装の違いはない。
基本的には、目的の違いによってメソッドを利用するだけならスタブオブジェクト、
メソッド呼び出しの検証を行うものをモックオブジェクトと呼ぶが、人によってまちまち。
UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
スタブの戻り値
スタブオブジェクトの戻り値は、モックの定義(後述)前だと
メンバ、メソッド共に、null(プリミティブ型の場合はデフォルト値)を返却する。
// プリミティブ型
assertThat(mock.x, is(0));
assertThat(mock.flag, is(false));
// オブジェクト
assertThat(mock.str, is(nullValue()));
assertThat(mock.decimal, is(nullValue()));
// スタブメソッド(定義前)
assertThat(mock.doSomething(), is(nullValue()));
スタブメソッドの定義
モックオブジェクトに定義されたスタブメソッドは、
「こういう引数が渡されるとこういう戻り値が返る」といった形で
戻り値を再定義することができる。
詳細は後述していくが、基本的な流れとしては
org.mockito.Mockitoクラスのwhenメソッドでorg.mockito.stubbing.OngoingStubbingクラスを
返却し、OngoingStubbingクラスに定義されている様々なstaticメソッドを利用する。
// 「doSomething()を呼び出す。すると、"想定値"が返却される。」
// といった風に自然言語的にメソッドを定義できる
when(mock.doSomething()).thenReturn("想定値");
// 再定義されたことで、doSomething()の戻り値が"想定値"になっている
assertThat(mock.doSomething(), is("想定値"));
その他のスタブメソッド
例外を送出するスタブメソッド
〜戻り値があるメソッドの場合〜
public void connectDBWithInfo(String user) throws SQLException {
// DBに接続する
return "DBの接続情報";
}
@Test
public void 例外が発生するスタブ() {
UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
try {
when(mock.connectDB("unExistUser")).thenThrow(new SQLException());
mock.connectDBWithInfo("unExistUser");
} catch (SQLException e) {
System.out.println("SQLExceptionの発生");
}
}
SQLExceptionの発生
〜戻り値がないメソッドの場合〜
public void connectDB(String user) throws SQLException {
// DBに接続する
}
@Test
public void 例外が発生するスタブ2() {
UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
try {
doThrow(new SQLException()).when(mock).connectDB("unExistUser");
mock.connectDB("unExistUser");
} catch (SQLException e) {
System.out.println("SQLExceptionの発生");
}
}
SQLExceptionの発生
〜任意の引数をとる形でテストしたい場合〜
テストケースで50通りの入力値をテストしたいとなった場合に、
今までの書き方だと50行のwhenメソッドが必要となる。
そういった場合はMockitoのMatchersクラスの提供するanyXXXメソッドを使用する。
public int returnConstNum(int anyNum) {
return 0;
}
@Test
public void 任意の値をとるメソッドの検証() {
UnfinishedLogic mock = Mockito.mock(UnfinishedLogic.class);
when(mock.returnConstNum(anyInt())).thenReturn(1);
// どんな数値を渡しても実測値が1になっている
assertThat(mock.returnConstNum(1), is(1));
assertThat(mock.returnConstNum(5), is(1));
assertThat(mock.returnConstNum(9999), is(1));
}
メソッドの呼び出し状況を検証する
モック的な利用方法。メソッドがどんな引数で、何回呼ばれたかを検証する。
Mockitoクラスのverifyメソッドを利用し、
verify(対象モックオブジェクト, 呼び出し回数).検証するメソッド
を基本の形として実装する。
@Test
public void verifyによる検証() {
List<String> mock = Mockito.mock(List.class);
mock.clear();
mock.add("arg1");
mock.add("arg1");
Mockito.verify(mock).clear();
Mockito.verify(mock, times(2)).add("arg1");
Mockito.verify(mock, never()).add("arg2");
}
〜verifyメソッドに渡せる値(VerificationMode)〜
メソッド | 判定できる呼び出し条件 |
---|---|
times(n) | n回 |
atLeastOnce() | 最後に呼び出されたメソッド |
atLeast(n) | n回以上(n ≦) |
atMost(n) | n回以上(n ≧) |
never() | 一度も呼ばれていない |
オブジェクトの一部をモック化する
Mockitoでは、Mockitoクラスのspyメソッドに実オブジェクトを渡すことで
実オブジェクトの一部をモック化することができる。
spyメソッドで作成されたオブジェクトのメソッドを呼び出すとき、
スタブメソッドを定義されていないメソッドは実オブジェクトが実行される。
@Test
public void 実オブジェクトの一部モック化() {
UnfinishedLogic sut = new UnfinishedLogic();
UnfinishedLogic spy = spy(sut);
when(spy.doSomething()).thenReturn("mockString");
// 再定義しているので定義した戻り値が返却される
assertThat(spy.doSomething(), is("mockString"));
// 再定義していないので実オブジェクトの戻り値 0 が返却される
assertThat(spy.returnConstNum(1), is(0));
}
スパイオブジェクト
プロダクションコードは筆者の記事のOutputLogクラスを参照。
@Test
public void スパイオブジェクトの作成() {
OutputLog sut = new OutputLog();
Logger spy = spy(sut.logger);
StringBuffer infoLog = new StringBuffer();
// org.mockito.stubbing.Stubberクラスを返却
doAnswer(new Answer<Void>() {
// 戻り値のないメソッドにコールバック関数を適用する
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// スパイオブジェクトで独自に追加する処理
infoLog.append(invocation.getArguments()[0]);
// 実際のメソッドの呼び出し
invocation.callRealMethod();
return null;
}
}).when(spy).info(anyString());
// テスト対象クラスのロガーをスパイオブジェクトに差し替える
sut.logger = spy;
sut.outputLog();
assertThat(infoLog.toString(), is("doSomething"));
}
doAnswerメソッド
スタブに対して、どのようにメソッドを実行するかを定義する。
引数にorg.mockito.stubbing.Answerインターフェースをとることで、
Answerインターフェースの実装クラスで定義されたメソッドを実行する仕組みを提供する。
具体的には、org.mockito.stubbing.Stubberクラスを返却する。
Answerインターフェース
下記のメソッドのみが定義されたインターフェース。よって、ラムダによる実装も可能。
T answer(InvocationOnMock invocation) throws Throwable
answerメソッドではスタブメソッドにおいて行う処理を柔軟に変更することができる。
InvocationOnMockクラス
実オブジェクトに関して様々な操作を行うためのクラス。
メソッド定義 | 処理内容 |
---|---|
Object callRealMethod() | 実オブジェクトのメソッドを呼び出す |
T getArgumentAt(int index, Class clazz) | メソッドの引数の位置を指定して取得する |
Object[] getArguments() | メソッドの引数を全て取得する |
Method getMethod() | 実オブジェクトのメソッドのメタ情報を取得する |
Object getMock() | モックオブジェクトを取得する |
参考文献
この記事は以下の情報を参考にして執筆しました。