動機
春は新卒研修の季節(僕も新卒)!新卒研修といったらなんだかんだ Java!学生のうちにあまり書く機会はないけど Java を使った大人の開発で書く必要があるものと言ったらテストコード!テストコードといったら Mockito!(イマココ)
本記事のゴール
「Mockito, PowerMockito を用いてモックを行う際に必要な準備(宣言や定義)の記述」を、Mock 対象についての特徴分けを元にして覚える・整理する。
はじめに
正直、Mockito 単体についてなら @opengl-8080 さんの Mockito使い方メモを読まれた方が良いです。
非常に詳しく書かれている上にわかりやすいです。
本記事は、PowerMockito と絡めつつ全体をさっぱりと理解する感じのテイストとなっています。
Mockito, PowerMockito について
既に知っているよという方は飛ばしてください。
Mockito(モキート) とは
主に Java のテストコードを書く際に用いられるライブラリのことで、目的は主に次の2つです。- 関数やオブジェクトの振る舞いを Mock する
(実際に動作するものを特定の値などを返す代わりのものに置き換える) - (Mock した)関数等の呼び出し回数についてテストを行う
よく Java のテスト用ライブラリである JUnit と組み合わせて用いられます。とても便利です。
また、Mock 化したオブジェクトについては、一部のメソッドは Mock 化せずに本来の動作を、一部のメソッドは Mock 化して特定の値を返すように設定することが可能です。
PowerMockito とは
Mockito の兄弟のようなもので、その名の通り Power のある Mockito です。存在する目的は Mockito と同じ(Mock)ですが、Mockito とは区別する必要があります。以後読むにあたっての前提知識
-
Mockito = for public, PowerMockito = for private, static
基本的に Mockito は public なメソッド(final, static なものも含む) を Mock します。
昔は final, static なものについて Mockito では Mock 化できなかったのですが、現在はできるようです。(バージョン等をご確認ください)
もし、バージョンが古い場合には final, static なものは public であっても PowerMockito を用いる必要があります。 -
Mock を inject するクラス(テスト対象のクラス)の明示
Mock を用いる場合は、injectMock アノテーションを用いて Mock を inject することを明示する必要があります。
@InjectMocks
targetClass instanceName;
参考
Mock オブジェクト宣言編
- オブジェクトの全てのメソッドが Mock 化された Mock オブジェクトを宣言したいのか、全メソッドが本来の動作をする Mock オブジェクト(パーシャルモックというらしい)を宣言したいのか
- MockitoとPowerMockitoのどちらを使うか
によって異なります。
どのような宣言をしたとしても、個別のメソッドの動作は以降で紹介する方法によって定義することができます。
ここからは次のようなクラスを扱った場合の記述を考えるものとします。
public class targetClass{
...
}
Mockito
- 全メソッドを Mock 化した Mock オブジェクトを宣言したい場合
mock アノテーション、または mock メソッドを用いて宣言する。
アノテーションを用いた場合、アノテーション記述時点ではMockオブジェクトは生成されないため、before アノテーションを用いてテスト実行前に初期化する必要があります。
@Mock
targetClass mockObjectName;
@Before
MockitoAnnotations.initMocks(this);
// or
targetClass mockObjectName = Mockito.mock(targetClass.class);
- 全メソッドが本来の動作をする Mock オブジェクト(パーシャルモック)を宣言したい場合
spy アノテーション、または spy メソッドを用いて宣言する。
@Spy
targetclass spyName = new targetClass();
// or
targetClass spyName = Mockito.spy(new targetClass());
より具体的に mock と spy の違いを知りたい場合は以下の参考をご覧になると良いと思います。
参考
PowerMockito
ここからは次のようなクラスを扱った場合の記述を考えるものとします。
private class targetClass{
...
}
- PowerMockito の Mock
PowerMockito を用いて Mock 化する必要があります。
また、static メソッドを使うためにあらかじめクラスを用意しておくため、@PrepareForTest を用いてクラスを宣言しておく必要があります。
@PrepareForTest({
targetClass.class
})
targetClass objectName = PowerMockito.mock(targetClass.class);
- PowerMockito の Spy
@PrepareForTest({
className.class
})
className objectName = PowerMockito.spy(new targetClass());
メソッド定義編
本記事では Mock 対象について次のような知識が与えられた時に、Mock クラス, Mock メソッドをどのように書けばいいかを整理します。以後質問は番号で呼びます。
具体的な書き方を整理する前に次にいくつか前提として留意しておくべき点を書いておきます。
同じ Mock メソッドを作る際に書き方は 1 つじゃない
void でないメソッドの mock 化については、then-系とdo-系の2種類の書き方があります。
例として、以下の2つのコードはほぼ全く同じMockを指定します。
when(mockName).methodName.thenReturn(returnValue);
doReturn(returnValue)when(mockName).methodName;
ただし、実際には細かい違いがあり、do-系のメソッドを使う方が好ましいとされているようです。
そのため、以降どちらの書き方もできる場合はdo-系を使うものとします。
参考
Mock メソッドの引数指定について
以後メソッドのMock宣言を行う際に、そのメソッドが引数をとる場合には「どのような引数が与えられた場合に Mock として動作するか」という点まで指定する必要があります。
具体的な数値や文字列を指定することもできますし、任意の整数や文字列などが与えられた場合として指定することもできます。
おおよそ、任意のものについては any型名() で、具体的な値と一致した場合を指定するには eq(value) で指定することができます。
参考の記事がとてもわかりやすい上に詳しく書かれているのでぜひお読みください。
// 任意の2つの文字列が copy メソッドに渡された時に "Success" という文字列を返す Mock 宣言
doReturn("Success").when(---).copy(anyString(), anyString());
//
参考
対象メソッドの区別とその宣言
主な Mock 化したいメソッドの区別
m-0. static
m-1. コンストラクタ
m-2. 実行回数を数えたいだけなので動作自体は関数本来の動作をさせたい
m-3. void メソッド
m-4. 値を返すメソッド
m-5. エラーを返したい
m-0. static
基本的なメソッド定義は以後に紹介するのですが、static なメソッドを Mock 化する場合は PowerMockito に対して PrepareForTest アノテーションでの宣言に加えて mockStatic を用いて static を Mock 化することを宣言する必要があります。
@PrepareForTest
({
target.class
})
@Before
public void setUpMock() throws Exception {
PowerMockito.mockStatic(targetMock.class);
}
m-1. コンストラクタ
ver3.5あたり(2020-)から Mockito に追加された mockConstruction を用いることで実装できます。
PowerMockito を用いる場合は PowerMockito.whenNew で返す値等を設定します。
また、その際に PrepareForTest アノテーションで new メソッドの呼び出し元のクラスを用意しておく必要があります。
try (MockedConstruction mocked = Mockito.mockConstruction(targetClass.class)) {
// この try の中では new targetClass が Mock を返す
}
@PrepareForTest
({
呼び出し元.class
})
doReturn(returnObject).whenNew(targetClass.class).someMethod(...)
参考
- Mockitoでfinalメソッドをモック化する
- @PreparedForTestに入るクラス、お前やったんかい(PowerMockでwhenNewを伴う場合)
- 公式 doc - Mocking Object Construction
m-2. 実行回数を数えたいだけなので動作自体は関数本来の動作をさせたい
Spy ではなく Mock で宣言した際、あるメソッドについては本来の動作をさせたい場合は doCallRealMethod を用います。
doCallRealMethod().when(className).methodName(methodsArguments);
m-3. void メソッド
Mock 対象が値を返さない(void)関数の場合は doNothing を利用して Mock を設定します。
doNothing().when(mockName).methodName(methodsArguments);
m-4. 値を返すメソッド
doReturn を用いて Mock の返り値を指定します。
doReturn(returnValue).when(mockName.methodName(methodsArguments));
m-5. エラーを返したい
doThrow を用いて Mock が返すエラーを指定します。
doThrow(new exceptionType).when(mockName).methodName(methodsArguments);
まとめ
私自身先月から Java を書き始めた人なので間違っている点や改善点等ございましたらコメントくださると幸いです。
構文自体はシンプルなものが多くとても使いやすいと思いますが、使っているであろう人数の割にエラーハンドリングの記事等が少ないように感じてそこが辛いところですね。
次回のMock動作検証(テスト)編でお会いしましょう。(やる気があったら)