りんごとバナナとエンジニア

エンジニア修行の記録

【Java】雰囲気でmockしていた自分のためのMockito再入門

Mockitoはテストの際に何度も使ったことがあるが、mockやspy, injectmocks等の用語の意味をなんとなくでしか理解しておらず、使う際に何度も詰まってしまっていた。このたび、公式ドキュメントを改めて読み直してみたのでまとめておく。

javadoc.io

mockとは

Mockitoでは、インターフェースやクラスをmockすると、そこに定義されたメソッドを呼んでもコンパイルエラーにならなくなる。また、mockは各メソッドが呼ばれた回数も記憶する。
ドキュメントには、例えとしてListインターフェースをmockしたコードが載っている。(もちろんこんなことをやる機会はほとんどないだろう)

 import static org.mockito.Mockito.*;

 List mockedList = mock(List.class);

 mockedList.add("one");
 mockedList.clear();

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

mockの意味はこれだけ。自分が勘違いしていたところでもあるが、mockだけをしてどこにもstubしない場合もある。例えば、単に verify を使ってメソッドの呼び出し回数をテストしたいだけの時などは、stubする意味がない。
また、上記でmockオブジェクトを定義するところの記述は、 @Mock アノテーションを使えばより簡潔に書ける。ただしこの場合、テストクラス内(またはその親クラス)のどこかに MockitoAnnotations.initMocks(testClass); の記載が必要となる。

stubとは

単にmockしただけだと、mockオブジェクトのメソッドは返り値の型に応じてデフォルトの値を返すことしかしない(int型なら0, boolean型ならfalseなど)。これではテストにならない。
そこで、特定の引数によって特定のメソッドが呼び出されたとき、その結果をoverrideすることができる。これがstubである。ナイフで結果を差し込むイメージだろうか。

LinkedList mockedList = mock(LinkedList.class);

when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

上の例では、 when を使っている部分がstubにあたる。

spyとは

Mockitoでは、ある特定のオブジェクトのspyを作ることができる。(mockと違い、対象はオブジェクトである)
spyの特徴は、メソッドを呼び出したときに、spyしたオブジェクト内で定義された本物の処理が呼ばれることにある。先程書いたように、普通のmockではデフォルトの値が返ってくるだけ。
さらに、spyにstubすることもできる。このように一部のメソッドだけstubすると、他のメソッドは本当に呼ばれるので、partial mockingということができる。なお、partial mockingはテストコードを複雑にするとして、使用が推奨されていない。

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

when(spy.size()).thenReturn(100);

spy.add("one");
spy.add("two");
System.out.println(spy.get(0));

System.out.println(spy.size());

上記の例では、 spy.add メソッドは本当に呼び出され、 "one" と "two" の2つの要素が追加される。しかし、 spy.size メソッドはstubされているため、sizeを取得すると100が返ってくる。

InjectMocks

@InjectMocks を指定したクラスに、mockオブジェクトやspyオブジェクトをinjectしてくれる。しかし、ドキュメントを読んでみると、どのような場合でもinjectできるわけではないようだ。
injectが成功するのは、以下の3つの場合に限られる。(spyを使う機会は少ないので、以下はmockの場合のみ書いている)

  • Constructor Injection: mockしたクラスが、InjectMocks指定されたクラスのコンストラクタ引数になっている
  • Property Setter Injection: コンストラクタに引数はないが、mockしたクラスをセットできるsetterがinjectMocks指定されたクラスに定義されている
  • Field Injection: コンストラクタに引数はないが、mockしたクラスをセットできるフィールドがinjectmocks指定されたクラスに定義されている

すなわち、mockしたクラスではない別の引数がコンストラクタにある場合、mockしたクラスを扱うsetterやfieldがあっても、injectに失敗する。