Mockito 3 + JUnit 5 で基本的なモック化とテストをするサンプルコード

## 概要

- Mockito 3 + JUnit 5 で基本的なモック化とテストをするサンプルコードを示す
- 今回の動作確認環境: Java 15 (AdoptOpenJDK 15.0.1+9) + Gradle 6.7 + Mockito 3.6.0 + JUnit 5.7.0 + macOS Catalina

## サンプルコード

### ソースコード一覧

```
├── build.gradle
└── src
    ├── main
    │   └── java
    │       ├── Client.java
    │       └── Worker.java
    └── test
        └── java
            └── ClientTest.java
```

### build.gradle

ビルド・テスト実行用 Gradle 設定ファイル。

```groovy
plugins {
  id 'java'
}

repositories {
  jcenter()
}

dependencies {
  // Junit 5 を導入
  // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
  testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'

  // Mockito 3 を導入
  // https://mvnrepository.com/artifact/org.mockito/mockito-core
  testImplementation 'org.mockito:mockito-core:3.6.0'

  // Mockito による JUnit 5 Extension ライブラリを導入
  // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
  testImplementation 'org.mockito:mockito-junit-jupiter:3.6.0'
}

test {
  // JUnit platform を使う設定
  useJUnitPlatform()

  // テスト時の出力設定
  testLogging {
    // テスト時の標準出力と標準エラー出力を表示する
    showStandardStreams true
    // イベントを出力する (TestLogEvent)
    events 'started', 'skipped', 'passed', 'failed'
    // 例外発生時の出力設定 (TestExceptionFormat)
    exceptionFormat 'full'
  }
}
```

### src/main/java/Worker.java

モック化の対象となるクラス。

```java
public class Worker {

  // 引数あり・戻り値ありメソッド
  public int ariari(int x) {
    throw new IllegalStateException("環境依存で発生するみたいな例外");
  }

  // 引数なし・戻り値なしメソッド
  public void nasinasi() {
    throw new IllegalStateException("環境依存で発生するみたいな例外");
  }
}
```

### src/main/java/Client.java

テスト対象のクラス。

```java
public class Client {

  private Worker worker = new Worker();

  // 引数あり・戻り値ありメソッドを呼び出す
  public int callAriari(int x) {
    return worker.ariari(x * 2); // 2倍にした値を Worker#ariari に渡す
  }

  // 引数なし・戻り値なしメソッドを呼び出す
  public int callNasinasi(int x) {
    worker.nasinasi(); // Worker#nasinasi を呼び出す
    return x * 2; // 2倍にした値を返す
  }
}
```

### src/test/java/ClientTest.java

テストクラス。
Worker クラスを Mockito 3 でモック化し、Client クラスのテストを JUnit 5 で実施する。

```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;

@ExtendWith(MockitoExtension.class) // Mockito による JUnit 5 エクステンション
public class ClientTest {

  // モックを注入するオブジェクト
  @InjectMocks
  private Client client = new Client();

  // モック化するオブジェクト
  @Mock
  private Worker mockedWorker;

  @Test
  public void 引数あり_戻り値あり() {
    // モックの振る舞い: Worker#ariari に 2 が渡されたときに 6 を返す
    doReturn(6).when(mockedWorker).ariari(2);
    // テスト: Client#callAriari に 1 を渡すとモックの Worker#ariari に 2 を渡して 6 が返ってくる
    assertEquals(6, client.callAriari(1));
  }

  @Test
  public void 引数なし_戻り値なし() {
    // モックの振る舞い: Worker#nasinasi を呼び出したときに何もしない
    doNothing().when(mockedWorker).nasinasi();
    // テスト: Client#callNasinasi に 1 を渡すとモックの Worker#nasinasi を実行して 2 が返ってくる
    assertEquals(2, client.callNasinasi(1));
  }

  @Test
  public void 例外発生() {
    // モックの振る舞い: Worker#ariari に 4 が渡されたときに例外を投げる
    doThrow(new IllegalArgumentException("モック例外")).when(mockedWorker).ariari(4);
    // テスト: Client#callAriari に 2 を渡すとモックの Worker#ariari に 4 を渡して例外が投げられる
    IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> client.callAriari(2));
    // テスト: 例外に想定したメッセージが含まれている
    assertEquals("モック例外", e.getMessage());
  }

  @Test
  public void 順次呼び出し() {
    // モックの振る舞い: Worker#ariari に 6 が渡されたときに例外を2回投げたあと 18 を返す
    doThrow(new IllegalArgumentException("モック例外1回目"))
      .doThrow(new IllegalArgumentException("モック例外2回目"))
      .doReturn(18)
      .when(mockedWorker).ariari(6);
    // テスト: Client#callAriari に 3 を渡すとモックの Worker#ariari に 6 を渡して例外が投げられる
    IllegalArgumentException e1 = assertThrows(IllegalArgumentException.class, () -> client.callAriari(3));
    assertEquals("モック例外1回目", e1.getMessage());
    // テスト: Client#callAriari に 3 を渡すとモックの Worker#ariari に 6 を渡して例外が投げられる
    IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> client.callAriari(3));
    assertEquals("モック例外2回目", e2.getMessage());
    // テスト: Client#callAriari に 3 を渡すとモックの Worker#ariari に 6 を渡して 18 が返ってくる
    assertEquals(18, client.callAriari(3));
  }
}
```

## テスト実行

Gradle で test タスクを実行した例。

```
$ gradle test
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :test

ClientTest > 引数なし_戻り値なし() STARTED

ClientTest > 引数なし_戻り値なし() PASSED

ClientTest > 引数あり_戻り値あり() STARTED

ClientTest > 引数あり_戻り値あり() PASSED

ClientTest > 例外発生() STARTED

ClientTest > 例外発生() PASSED

ClientTest > 順次呼び出し() STARTED

ClientTest > 順次呼び出し() PASSED

BUILD SUCCESSFUL in 19s
3 actionable tasks: 3 executed
```

## 参考資料

- [Mockito \(Mockito 3\.6\.0 API\)](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html)
- [MockitoExtension \(junit\-jupiter 3\.6\.0 API\)](https://javadoc.io/static/org.mockito/mockito-junit-jupiter/3.6.0/org/mockito/junit/jupiter/MockitoExtension.html)
- [Assertions \(JUnit 5\.7\.0 API\)](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html)
- [JUnit 5 \+ Gradle による Java の自動テスト導入 \- Qiita](https://qiita.com/niwasawa/items/cfcd37a3c2a795c336ba)