JUnit5とMockitoで単体テスト


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の場合は書き方も違うので注意。

build.gradle.kts
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};
    }
}

テストコード作成

MainServicegetSum()をテストするコードを作成する。

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-jupitertestImplementationで追加しないといけない。
testCompileOnlyで追加しても動かないので注意。

所感

Mockitoは便利なのだが、使い方がわからず、あるいはモックしたいものがうまくモックされず、グーグル先生に頼ることが多い。

すぐに目的の情報が見つかればよいのだが、「SpringBootでMockitoするなら〜」とか、「JUnit4じゃないと動かないサンプルコード」とか、今それは必要な情報じゃないんだっていうサイトが上位に来て、なかなか目的の情報にたどりつけない事が多い。

たとえば、「ここをモックに置き換えたいのにうまく動かない、privatedだからかな?」と思ってprivateなフィールドをモックにしたいときどうするか、って思って調べたらPowerMockなどがあるって記事がヒットするけど、今回みたいに別にPowerMock使わなくてもできるし。結果的にGradleの使い方間違ってたりするだけっていう落ちだったりするし。

「テストコードになんでこんな時間費やさなきゃいけないんだ」ってストレスが貯まり始めると、テストコード書くのがおざなりになりがち。