JUnit5 と Mockito を使ったテストコードの解説

# 概要
このドキュメントでは、JUnit5 および Mockito を使用して UserController クラスをテストするコードについて解説する。各アノテーションやメソッドの役割、モックの設定方法について取り上げる。

# 1. アノテーションの解説

### `@Test`
- **役割:**  
  テストメソッドであることを示す。JUnit5 ではこのアノテーションが付与されたメソッドがテストとして認識される。

### `@ExtendWith(MockitoExtension.class)`
**役割:**  
  `Mockito` を JUnit5 で使用するための拡張機能を提供する。  
  これにより、`@Mock` や `@InjectMocks` アノテーションが有効になる。  

### `@InjectMocks`
**役割:**  
  テスト対象クラスのインスタンスを作成し、`@Mock` で作成されたモックオブジェクトを自動的に注入する。  

### `@Mock`
**役割:**  
  モックオブジェクトを作成する。 `when()`メソッドで動作を定義することで、特定の結果を返すように設定できる。

### `@DisplayName`
**役割:**  
  テストケースにわかりやすい名前を付ける。実行結果レポートで表示され、テストの意図が明確になる。日本語の説明も可能である。

# 2. メソッド解説

### `when()...thenReturn()`
- **役割:**  
  モックオブジェクトの特定のメソッドが呼ばれたときに、指定した値を返すように設定する。
- **例:**  
  ```java
  when(userService.findByUserId(TEST_USER_ID)).thenReturn(Optional.empty());
- **解説:**  
  userService.findByUserId(TEST_USER_ID) が呼ばれると、常に Optional.empty() が返る。これにより「ユーザーが見つからない」ケースをシミュレートする。

### `anyInt()`
- **役割:**  
　任意の整数値が引数として渡された場合でも、モックが反応するように設定する。
- **例:**  
  ```java
  when(userService.findByUserId(anyInt())).thenReturn(userEntity);
- **解説:**  
  userService.findByUserId() が任意の整数で呼び出されても、userEntity を返す。

### `verify()`
- **役割:**  
  テスト対象メソッドが特定のメソッドを呼び出したかを検証する。
- **例:**  
  ```java
  verify(redirectAttributes).addFlashAttribute("flashError", "指定されたユーザーが見つかりません。");
- **解説:**  
  テスト対象メソッドが addFlashAttribute() を呼び出したかを確認する。

### `verifyNoInteractions()`
- **役割:**  
  モックオブジェクトに対して、一切のメソッドが呼び出されていないことを検証する。
- **例:**  
  ```java
  verifyNoInteractions(model);
- **解説:**  
  modelオブジェクトに対して一切の操作が行われていないことを確認する。


サンプルコード

  ```java
/**
 * UserControllerのテストクラス
 */
@ExtendWith(MockitoExtension.class)
public class UserControllerTest {

    @InjectMocks
    private UserController controller;

    @Mock
    private UserService userService;

    @Mock
    private RedirectAttributes redirectAttributes;

    @Mock
    private HttpSession session;

    @Mock
    private Model model;

    private static final String SHOW_TEMPLATE_PATH = "user/show";
    private static final String REDIRECT_INDEX_PATH = "redirect:/user/index";
    private static final int TEST_USER_ID = 1;
    private static final Integer RESTORE_SESSION_REQUESTED = 1;
    private static final ControllerTestHelper controllerTestHelper = new ControllerTestHelper();

    /**
     * ユーザーが見つからない場合のテストケース.
     */
    @DisplayName("ユーザーが見つからない場合のテストケース.")
    @Test
    public void testShow_UserNotFound() {
        // 準備
        when(session.getAttribute("logined_user_name")).thenReturn("テストユーザー");
        when(userService.findByUserId(TEST_USER_ID)).thenReturn(Optional.empty());

        // 実行
        String result = controller.show(TEST_USER_ID, model, session, redirectAttributes);

        // 検証
        assertEquals(REDIRECT_INDEX_PATH, result);
        verify(redirectAttributes).addFlashAttribute("flashError", "指定されたユーザーが見つかりません。");
        verify(redirectAttributes).addAttribute("restoreSession", RESTORE_SESSION_REQUESTED);
        verifyNoInteractions(model);
    }

    /**
     * ユーザーが見つかり、レコードが存在しない場合のテストケース.
     * @throws ParseException 日付の形式が間違っていた際に投げられる例外.
     */
    @DisplayName("ユーザーが見つかり、レコードが存在しない場合のテストケース.")
    @Test
    public void testShow_WithoutRecords() throws ParseException {
        // 準備
        List<RecordListDto> emptyRecordList = new ArrayList<>();
        val userEntity = Optional.ofNullable(controllerTestHelper.createTestUserEntity());
        when(userService.findByUserId(anyInt())).thenReturn(userEntity);
        when(userService.findResultByUserId(anyInt())).thenReturn(emptyRecordList);

        // 実行
        String result = controller.show(TEST_USER_ID, model, session, redirectAttributes);

        // 検証
        assertEquals(SHOW_TEMPLATE_PATH, result);
        verify(model).addAttribute("user", userEntity.get());
        verify(model).addAttribute("hasRecordList", false);
    }

    /**
     * ユーザーが見つかり、レコードが存在する場合のテストケース.
     * @throws ParseException 日付の形式が間違っていた際に投げられる例外.
     */
    @DisplayName("ユーザーが見つかり、レコードが存在する場合のテストケース.")
    @Test
    public void testShow_WithRecords() throws ParseException {
        // 準備
        val recordList = controllerTestHelper.createTestRecordList();
        val userEntity = Optional.ofNullable(controllerTestHelper.createTestUserEntity());
        when(userService.findByUserId(anyInt())).thenReturn(userEntity);
        when(userService.findResultByUserId(anyInt())).thenReturn(recordList);

        // 実行
        String result = controller.show(TEST_USER_ID, model, session, redirectAttributes);

        // 検証
        assertEquals(SHOW_TEMPLATE_PATH, result);
        verify(model).addAttribute("user", userEntity.get());
        verify(model).addAttribute("hasRecordList", true);
    }
}