JUnit5+Mockitoで単体テスト
背景
最近Java11で開発できるプロジェクトにアサインされた。まともに単体テストコードを作成する(!?)かなり丁寧なプロジェクトなのだが、
ここ1〜2年くらいJavaのプロジェクトに関わっていなかったため、「昔はこれで動いたのに今だと動かないんだ」ってことがいくつか重なったので、備忘録として記事にすることにした。
バージョンなど
- Java 11 (OpenJDK)
- Gradle 6.5
- Gradle 5以降でかなり変わったよね
- JUnit5 (junit-jupiter)
- JUnit4のときからかなり変わったよね
- Mockito
- JUnitのバージョンによってやり方違うよね
Gradleでプロジェクト雛形作成
gradle init
でプロジェクトディレクトリを作成。
Groovyだけじゃなくてkotlinでもbuild.gralde
かけるようになったみたい。知らなかった。
$ mkdir pjname
$ cd pjname
$ gralde init --type java-application
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2] 2
Select test framework:
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4] 4
Mockitoを依存関係に追加
https://site.mockito.org/ に追加方法が書いてあるけど、testCompile
はGradle6.5では使えないし、kotlinの場合は書き方も違うので注意。
dependencies {
// 中略
// Mockito https://site.mockito.org/
testImplementation("org.mockito:mockito-core:2.+")
testImplementation("org.mockito:mockito-junit-jupiter:2.+")
}
サンプルコード作成
テスト対象となる、サンプルクラスを作成。
MainService
クラスからSubService
クラスを呼び出す想定としている。
public class MainService {
private SubService subService = new SubService();
public int getSum() {
int sum = 0;
for (int i : subService.getRandomIntegerList()) {
sum += i;
}
return sum;
}
}
public class SubService {
public int[] getRandomIntegerList() {
return new int[]{8, 7, 2, 3, 6, 4, 5, 8, 4, 0};
}
}
テストコード作成
MainService
のgetSum()
をテストするコードを作成する。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) // JUnit5でMockito使うには必要
class MainServiceTest {
@Mock // モック(スタブ)に置き換えたいインスタンスに定義。すべてのメソッドがモックになる
//@Spy // 一部のメソッドだけモックにしたいときはこれを定義
private SubService subService;
@InjectMocks // @Mockでモックにしたインスタンスの注入先となるインスタンスに定義
private MainService mainService;
@Test
public void testGetSum() {
Mockito.when(this.subService.getRandomIntegerList()) // このモックを呼び出したとき
.thenReturn(new int[]{10, 20, 30, 40}); //このデータを返すようにモックする
Assertions.assertEquals(this.mainService.getSum(), 100);
}
}
@ExtendWith
ってなに?
JUnit4では@RunWith
だったもの。JUnit5ではこれになった。
また、Mockitoを使うときは、Mockitoが専用のExtension(MockitoExtension
)を用意しているのでこれを指定しないといけない。
っと言うことが公式ドキュメントに書いてあるかと思ったのだが、
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
45. New JUnit Jupiter (JUnit5+) extension
↑このリンク先がない・・・たぶん以下だとおもうけど
https://javadoc.io/doc/org.mockito/mockito-junit-jupiter/latest/index.html
ちなみに、MockitoExtension
を使うには、graldeの依存ライブラリにmockito-junit-jupiter
をtestImplementation
で追加しないといけない。
testCompileOnly
で追加しても動かないので注意。
所感
Mockitoは便利なのだが、使い方がわからず、あるいはモックしたいものがうまくモックされず、グーグル先生に頼ることが多い。
すぐに目的の情報が見つかればよいのだが、「SpringBootでMockitoするなら〜」とか、「JUnit4じゃないと動かないサンプルコード」とか、今それは必要な情報じゃないんだっていうサイトが上位に来て、なかなか目的の情報にたどりつけない事が多い。
たとえば、「ここをモックに置き換えたいのにうまく動かない、privatedだからかな?」と思ってprivate
なフィールドをモックにしたいときどうするか、って思って調べたらPowerMock
などがあるって記事がヒットするけど、今回みたいに別にPowerMock
使わなくてもできるし。結果的にGradleの使い方間違ってたりするだけっていう落ちだったりするし。
「テストコードになんでこんな時間費やさなきゃいけないんだ」ってストレスが貯まり始めると、テストコード書くのがおざなりになりがち。