Junit5+Mockitoによる単体テストメモ

# はじめに
Junit5とMockitoを使用したテストコードを1から記載する機会があったため、使い方を振り返れるようにまとめた自分用メモです。
モックの記述方法が中心の内容となります。

# 依存関係
## Junit5
SpringBootの場合はspring-boot-starter-testに含まれているため記載不要です。
```xml:pom.xml
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.junit</groupId>
				<artifactId>junit-bom</artifactId>
				<version>5.8.2</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
			<plugin>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>
		</plugins>
	</build>

```
Junit5ユーザーガイドの[1.4.3. Example Projects](https://junit.org/junit5/docs/current/user-guide/#overview-getting-started-example-projects)にサンプルプロジェクトへのリンクがあり、そちらを参考にしました。

## Mockito
SpringBootの場合はspring-boot-starter-testに含まれているため記載不要です。
mockito-inlineはSpringBootでも追加が必要でした。
```xml:pom.xml
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>4.6.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>4.6.1</version>
        <scope>test</scope>
    </dependency>
    <!-- mockito-inlineは、staticメソッド、コンストラクタのモック時に必要 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>4.6.1</version>
    </dependency>
```

# テストクラスの記述方法
## Mockitoを使用する際のクラスへのアノテーション付与
```java
@ExtendWith(MockitoExtension.class)  // 必須
class XXXTest {
    // テストコード
}
```
`@ExtendWith(MockitoExtension.class)`がない場合、Mockitoが使用できないため、実行時にエラーになります。

## Mock
テスト対象クラスの特定の変数をモックに置き換え、実行時の挙動を意図的に変更できる。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {
    
    @Mock // 実行時にモックが代入される
    private MockTarget mock;

    @InjectMocks // 実行時にコンストラクタ引数にモック・スパイを設定したインスタンスが代入される
    private TestTarget target;

    @Test
    void テスト() {
        // モックの挙動を設定
        // 1.引数がないメソッドに対する設定
        // thenThrowでExceptionの返却も可能。
        when(mock.methodA()).thenReturn(モックから返却したい値);

        // 2.引数があるメソッドに対する設定
        // any()は、anyInt(),any(引数の型.class)で型の指定が可能。
        when(mock.methodB(any())).thenReturn(モックから返却したい値);

        // 3.呼び出すたびに返却値を変更する
        when(mock.methodC())
            .thenReturn(モックから返却したい値1)   // 1回目の返却値
            .thenReturn(モックから返却したい値2);  // 2回目の返却値

        // 4.voidメソッドの設定
        // doReturn類とthenReturn類でwhenの記述方法が違うので注意
        doNothing().when(mock).voidMethod();

        assertEquals(モックから返却したい値, mock.methodA()); // モックからの返却値が返却される。
    }
}
```
## Spy
テスト対象クラスの特定の変数の特定のメソッドのみの挙動を意図的に変更できる。
Spy対象のメソッド以外は本来と同じ挙動をする。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {

    @Spy // スパイ対象に付与
    private SpyTarget spy;

    @Spy // テスト対象クラスのメソッドをスパイ対象にする場合はここに付与
    @InjectMocks
    private TestTarget target;

    @Test
    void テスト() {
        // スパイの挙動を設定
        // SpyではthenReturnの記載方法はできない。
        // doReturnでの記載以外はMockと同じ。
        doReturn(スパイしたメソッドから返却したい値).when(spy).methodA();
        
        assertEquals(スパイしたメソッドから返却したい値, spy.methodA()); // スパイしたメソッドからの返却値が返却される。
    }
}
```

## Captor
モック・スパイしたメソッド呼び出し時の引数を取得する。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {

    @Captor 
    private ArgumentCaptor<キャプチャーしたい引数の型> argumentCaptor;

    @Mock
    private MockTarget mock;

    @InjectMocks
    private TestTarget target;

    @Test
    void テスト() {
        // モックの挙動設定時に引数の値を取得する記述をする
        // capture()で引数の値を取得
        when(mock.methodA(argumentCaptor.capture())).thenReturn(モックから返却したい値);

        // getValue()でキャプチャーした値を取得
        assertEquals(引数に設定される期待値, argumentCaptor.getValue());
    }
}
```

## MockedStatic
staticメソッドからの返却値を変更したいときに使用。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {

    @Test
    void テスト() {
        // try句内でのみモックが有効。try-with-resources文を使用しない場合は、別途close()が必要。
        try (MockedStatic<モック対象クラス> staticMock = mockStatic(モック対象クラス.class)) {
            // モックの挙動を設定
            staticMock.when(() -> モック対象クラス.statcMethod(anyInt())).thenReturn(モックから返却したい値);
        
            assertEquals(モックから返却したい値, モック対象クラス.statcMethod(1));
        }
    }
}
```

## MockedConstruction
インスタンス生成時にモックに置き換えたいときに使用。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {

    @Test
    void テスト() {
        // try句内でのみモックが有効。try-with-resources文を使用しない場合は、別途close()が必要。
        try (MockedConstruction<モック対象クラス> staticMock = mockStatic(モック対象クラス.class)) {
            // モックの挙動を設定
            モック対象クラス mock = new モック対象クラス();
            when(mock.methodA()).thenReturn(モックから返却したい値);
        
            assertEquals(モックから返却したい値, mock.methodA());
        }
    }
}
```
[公式](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48)では、上記の記述方法でできると解説されていますが、インスタンス生成時に置き換えたモックのメソッドから別のモックを返却したい時にモックに置き換わらなかったため以下の記述方法を使用。
```java
@ExtendWith(MockitoExtension.class)  
class XXXTest {

    @Test
    void テスト() {
        // try句内でのみモックが有効。try-with-resources文を使用しない場合は、別途close()が必要。
        try (MockedConstruction<モック対象クラス> staticMock = mockStatic(モック対象クラス.class,
                (mock, context) -> {
                    when(mock.methodA()).thenReturn(モックから返却したい値);
                })) {

            モック対象クラス target = new モック対象クラス();
            assertEquals(モックから返却したい値, target.methodA());
        }
    }
}
```

# おわりに
今回まとめた内容である程度のテストはできるのではないでしょうか？
Mokitoの公式ドキュメントは個人的に記載にばらつきがあると思ったため、使いそうなものをまとめてみました。
さらに詳細が知りたくなった方は是非公式ドキュメントを見てみてください。

# 参考文献
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
- [Mockito (Mockito 4.6.1 API)](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html)
- [How to use the Mockito's inline mock maker
](https://www.davidvlijmincx.com/posts/writing_higher_quality_tests_with_mockitos_inline_mock_maker/)
- [mockito でコンストラクターの mock を使ったテストをしたい](https://keyno63.hatenablog.com/entry/2021/09/24/234828)