テストコードを書く際に、モックして書くことがあります。
モック用のライブラリには「Mockito」等がありますが、今回は「JMockito」学ぶ機会があったのでこちらを利用してモック化してみたいと思います。
Mock(モック)
モック化することでテスト対象が依存するクラス、メソッドの振る舞いを定義することが可能です。
(似たような言葉としてMockの他にSpy,Stubという言葉があります)
モック化するメリットとしては以下のようなことが挙げられると思います。
・依存するクラスが存在していたとしてもMock化することでテストコードが書ける
・外部に依存しないテストが可能
・モック化したメソッドの振る舞いに対してテスト結果が正しいかを確認することが可能
・モックしたメソッドの引数・実行回数をチェックできる
【イメージ】
下記のメインクラスではCalcクラスを生成してdoubleNumメソッドを利用しています。
実際の実装は引数の値を2倍にするものです。
public class Main {
public static void main(String[] args) {
int a = 0;
Calc cal = new Calc();
// 引数の値を2倍にして返却
int result = cal.doubleNum(a);
System.out.print(result);
}
}
Calcクラスをモック化するとイメージとしては下記のようになります。
実装は2倍にするメソッドですが、モック化することで実装が空になり自身で振る舞いを定義する必要があります。3倍にして返却と設定しています。
正常に値を返却するではなくエラーを発生させることも可能です。なのでエラーを発生させてのテストも簡単にすることが可能です。
前提
今回テスト対象とクラスは「DiaryService.java」という自作したクラスになります。
このクラス内で実施しているdaoクラスをモック化していきます。
※内部の処理は気にしなくて大丈夫です。
public List<Diary> findList(GetForm form) {
return dao.findList(form);
}
<Diary>・・・データを格納するようのEntityクラス
dao.findList(form)・・・formクラスに格納された条件に合致するデータ取得するクラス
(DBよりSELECT文を実行してデータを取得)
daoはInterfaceでも受け取れるようにしている
参考までにこちらのクラスは下記で作成したものとなります。
実装①(Expectations,Verifications)
モック化したクラスを利用してテストコードを記載したいと思います。
定義するために「Expectations」を利用します。
Mock化したクラスのパラメータや呼び出し回数をチェックするために「Verifications」を利用します。
package diary;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import diary.entity.Diary;
import diary.form.GetForm;
import diary.repository.IDiaryDao;
import diary.service.DiaryService;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
public class DiaryServiceMockTest {
// モック化
@Mocked
IDiaryDao dao;
@Test
void 正常() {
// 振る舞いを定義
new Expectations() {{
dao.findList((GetForm) any);
List<Diary> list = Arrays.asList(new Diary(), new Diary(), new Diary());
result = list;
}};
// パラメータ設定
GetForm form = new GetForm();
form.setCategory("1");
// テスト対象クラスをインスタンス化
DiaryService service = new DiaryService(dao);
// 実行
List<Diary> result = service.findList(form);
// Mockクラスの検証
new Verifications() {{
// パラメータ格納用
GetForm paramCheck = new GetForm();
// 実行回数チェックのため記載 + withCapture()にてパラメータ取得
dao.findList(paramCheck = withCapture());
// 実行回数確認
times = 1;
// パラメータチェック
assertEquals("1", paramCheck.getCategory());
}};
// findListメソッド戻り値確認
assertEquals(3, result.size());
}
}
実装②(new MockUp)
今度はモック化したクラスの定義を設定するのではなく、既にインスタンスが作成されたクラスの一部メソッドの振る舞いを変更したいと思います。
実装①ではモック化し空になったクラスの振る舞いを定義するために「Expectations()」を利用しました。実装②ではモック化していないクラスの一部メソッドの振る舞いを変更(差し替える)することができるのでそれを実装していきます。
テスト対象のメソッドで呼び出している同じクラス内の別メソッドをモック化したい場合などに用いられます。このパターンではないですが、実装してみます。
package diary;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import diary.entity.Diary;
import diary.form.GetForm;
import diary.repository.DiaryDao;
import diary.service.DiaryService;
import mockit.Mock;
import mockit.MockUp;
@SpringBootTest
public class DiaryServiceMockTest2 {
// テストクラス 依存するクラスをインスタンス化
@Autowired
DiaryService service;
@Test
void 正常() {
new MockUp<DiaryDao>() {
@Mock
public List<Diary> findList(GetForm form) {
// パラメータチェック
assertEquals("1", form.getCategory());
// 戻り値設定
List<Diary> list = Arrays.asList(new Diary(), new Diary(), new Diary());
return list;
}
};
// パラメータ設定
GetForm form = new GetForm();
form.setCategory("1");
// 実行
List<Diary> result = service.findList(form);
// findListメソッド戻り値確認
assertEquals(3, result.size());
}
}
最後に
モック化することで他クラスに依存しないでテストすることが可能となります。
ここで紹介したこと以外にもモック用のライブラリを使用することで様々なことができます。
テストコードを書けるだけでなく、どういう方針でテストコードを書くのが決めれるようになりたいな〜