##はじめに
現在携わっているプロジェクトでJUnit + Mockito + PowerMock を使うことになったのですが
そもそもJUnit自体まともに触ったことがないわたしにとって全てが未知の領域。
「Mockito」でggってみても、なかなか知識0の人がすぐ使えるようになる説明されている記事が少ない。というか日本語が少ない!
日本語で説明がないとなかなか理解できないんですよね。。
ということで、最近ようやく少しだけ飲み込めたので何も知らなかった私に向けてこの記事を書きます。
今回はMockito編です。
特に、テスト対象クラスとは別クラスのメソッドをモック化したい場合について書きます。
##Mockitoにできないこと
まずMockitoには、できないことがたくさんあります。
万能ではなく、モック化できるメソッドは限られるのです。
モック化できないのが
- privateメソッド
- staticメソッド
- protectedメソッド
- インスタンス生成(new)
以上の4種類です。(多分)
あとは抽象クラスなんかもモック化できなかったと思います。(多分)
それ以外であれば基本的にmockitoでモック化することができます(多分)。
上の4つをモック化するには、Mockitoとは別でPowerMock
を使用しなければいけません。
呼び出したいメソッドのあるクラスをモック化
まず、モック化したメソッドを呼び出すにはそのクラスをモック化します。
モック化の方法は2種類。どちらか片方だけで大丈夫です。
① クラス直下に@Mock
② mock()
メソッドを使う
public class TestClass {
// ① クラス直下に@Mockでモックインスタンスの宣言
@Mock
private Entity entity; // これは宣言のみで、モックオブジェクトは生成されない
@Test
public void test001() {
// ② mock()メソッドを使う
Entity entity = mock(Entity.class); // これだけでモックオブジェクトが生成される
}
}
①の場合、この宣言とは別に初期化を行う必要があります。
②はテストクラスの中で毎度クラスをモック化します。これは初期化の必要はありません。
モックの初期化
①の@Mock
の場合モックオブジェクトはまだ生成されないので、初期化する必要があります。
public class class() {
// @Mockのパターン
@Mock
private ResultEntity entity;
@Before // @Beforeアノテーションをつけ、各テストメソッドの実行前に毎回実行。
private void initMocks() {
MockitoAnnotations.initMocks(this); // 初期化のおまじない
}
}
①の方法は、テストケースが複数ありモック化したいクラスが何度も出てくる場合に有効かと思います。
テスト対象クラスにモックを注入
@InjectMocks
アノテーションを使い、テストクラスの直下でテスト対象クラスにモックを注入します。
public class TestClass {
@Mock
Entity entity; // モック化したクラス
@InjectMocks
Human human; // テスト対象クラスにモックを注入
}
一応これだけでモック化した上でテストを行うことはできるのですが
カバレッジを取得する際はテスト対象クラスのインスタンスが呼ばれないと100%にならないので、カバレッジを100%にする場合は
@InjectMocks
Human human = new Human();
このように書けば大丈夫です。
これでクラスのモック化ができたので、以下でケース別に実際にメソッドをモック化していきます。
##戻り値のあるpublicなメソッドの場合
public String
など
この場合、メソッドをモック化する書き方はなぜか2種類あります。
###1.thenメソッド
Mockito.when(モックインスタンス).メソッド(任意の引数).thenReturn(任意の戻り値);
// モックされたクラスのメソッドが呼ばれた時、この戻り値を返すよ
これでメソッドはモック化され、実際にメソッドは実行されず任意の戻り値を返すだけのモックとなります。
ちなみに、頭のMockito.
は、あってもなくてもどちらでも大丈夫です。
PowerMock
でも同じような書き方をするため、同時に使用する場合は使い分ける必要があるため、頭につけたほうが分かりやすくなると思います。
###2.doメソッド
Mockito.doReturn(任意の戻り値).when(モックインスタンス).メソッド(任意の引数);
// この戻り値を返すよ、モックされたクラスのメソッドが呼ばれた時
①と②、どちらも全く同じ挙動をとります。
以下で説明しますが、thenメソッドでは対応できない場合があるので、doメソッドで統一しておけば間違いないようです。
##戻り値のないpublicvoidなメソッドの場合
public void
この場合は、doメソッドでしか記述できません。
Mockito.doNothing().when(モックインスタンス).メソッド(任意の引数);
// 何も返さないよ、モッククラスのメソッドが呼ばれた時
thenNothing()というメソッドは無いようです。
##モック化したメソッドの引数を検証する(ArgumentCaptor)
メソッドはモック化したのでその中を検証する必要はありませんが、モック化したメソッドに渡す引数は正しいかという検証は必要いなってくるかと思います。
そんな時に使えるのがArgumentCaptor
です。
ArgumentCaptor<型> 変数 = ArgumentCaptor.forClass(型.class);
// forClass()メソッドで、その型の容れ物(変数)をつくるよ
capture()メソッド
上の変数をメソッドのモック化をする時に引数として渡すと、実際に渡される引数をキャプチャして変数に格納しれくれます。
その時に、capture()
メソッドをつけます。
Mockito.doReturn("戻り値").when(クラス).メソッド(変数.capture());
// forClass()メソッドで、その型の容れ物(変数)をつくるよ
これで、変数に実際のソースで渡される引数が格納されました。
####格納されたキャプチャ情報の取得
変数.getValue(); // ①
変数.getAllValues().get(index); // ②
このどちらかのメソッドを使うことで、変数の型に応じた値を取得できます。
①引数一つだけを検証するのであれば、getValue()
メソッドで格納された情報を取得できます。
②ArgumentCaptorは、引数に何度も渡すことができ、引数の数だけ結果を格納することができます。(同じ型の場合に限る。)
その場合は配列のような形で格納されるので、getAllValues()
メソッドでその値全てを取得。get(index)
でそのうちひとつの値を取得できます。
実際には変数を使い回してしまうと、何番目がどこの引数に渡っているものなのか判断しにくくなるので、基本的には引数の数だけArgumentCaptor
の容れ物を宣言したほうが良いと思います。
ArgumentCaptor<String> str = ArgumentCaptor.forClass(String.class);
Mockito.doReturn("戻り値").when(Class).getStr(str.capture());
assertTaht(str.getValue(), is(引数の期待値));
使い方としてはこんな感じになると思います。
##モック化したメソッドの呼び出し回数を検証する(verify)
メソッドをモック化すると、実際にはそのメソッドを呼び出さないので、モック化したメソッドがちゃんと呼ばれているかどうかという検証も必要になってくるかと思います。
その時に使うのがverify()
メソッドです。
Mockito.verify(モックインスタンス, times(index)).モックメソッド(引数);
verify()
メソッドの第一引数はモックインスタンス、第二引数にtimes()
メソッド、その引数にintで期待値を渡すと、そのメソッドが何回呼ばれたか検証できます。
##戻り値のないメソッドはverify()のみでモック化できる
どういうことかと言いますと
戻り値のないメソッドのモック化は戻り値のないpublicvoidなメソッドの場合で書いた通りなんですが
メソッドの呼び出し回数を検証したい場合には、doNothing()
、verify()
両方行う必要はなく、verify()
のみで2つ同時に行うことができます。
同時に行うというよりは、verify()した時点で戻り値のないメソッドとしてモック化ができているということです。
ただ注意したいのは、戻り値のあるメソッドの場合はverify()でモック化できないということです。
verify()は戻り値をモック化することができないからです。
// doNothing()でモック化を行わなくても、これだけでok
Mockito.verify(モックインスタンス, times(index)).モックメソッド();
ちなみに、上記の場合+引数の検証をしたい場合は
// doNothing()でモック化を行わなくても、これだけでok
Mockito.verify(モックインスタンス, times(index)).モックメソッド(変数.capture());
とすることで、引数もこの時に格納できます。
##最後に
これらを覚えれば、ある程度戦えるようになるのではと思います。
同クラスの他メソッドのモック化、newのモック化なども今後書いていく予定です。