JUnit 5 × Java 21で@ExceptionHandlerのユニットテストを実装する
投稿日:2024/07/28
目標
- Spring Bootプロジェクトで実装されている@ExceptionHandlerのユニットテスト(UT)の実装をします
- 特定のExceptionをキャッチする@ExceptionHandlerが期待通りに動作しているかを検証します
条件
- Spring Bootプロジェクトで@ExceptionHandlerを使用したエラーハンドリングが実装されていること
- JUnit 5が導入されていること
- Java 21を使用していること
テストコードの実装
- ここでは以下のファイルが実装されていることを想定しています
ErrorController:テスト対象のコントローラ(hasErrorがtrueの場合にExceptionをスローする)
@Controller
public class ErrorController {
@GetMapping(value = "test")
public String hoge(@RequestParam(name = "hasError") boolean hasError, Model model) throws Exception {
if (hasError) throw new Exception("An unexpected error has occurred.");
model.addAttribute("status", "OK");
return "test";
}
}ErrorHandlerController:Exceptionをキャッチするコントローラ(Exceptionをキャッチした場合にerror.htmlを表示する)
@ControllerAdvice
public class ErrorHandlerController {
@ExceptionHandler({Exception.class})
public String handleException(Exception e) {
return "error";
}
}
1. ErrorControllerTest.javaを新規作成し、以下の内容を実装します。
package sample.application.controller;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@ExtendWith(SpringExtension.class)
@ExtendWith(MockitoExtension.class)
@SpringBootTest
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
})
public class ErrorControllerTest {
@InjectMocks()
ErrorController errorController;
private MockMvc mockMvc;
@BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(errorController)
.setControllerAdvice(new ErrorHandlerController())
.build();
}
@Test
@DisplayName("Exceptionがスローされ、exception handlerにキャッチされてerror.htmlが表示されること")
public void hasErrorTrue() throws Exception {
// Assert
mockMvc.perform(get("/test").param("hasError", "true"))
.andExpect(status().isOk())
// エラーオブジェクトの検証
.andExpect(result -> assertInstanceOf(Exception.class, result.getResolvedException()))
// エラーメッセージの検証
.andExpect(result -> assertEquals("An unexpected error has occurred.", result.getResolvedException().getMessage()))
// 表示されるhtml(view)の検証
.andExpect(view().name("error"));
}
@Test
@DisplayName("Exceptionがスローされず、test.htmlが表示されること")
public void hasErrorFalse() throws Exception {
// Assert
mockMvc.perform(get("/test").param("hasError", "false"))
.andExpect(status().isOk())
.andExpect(model().hasNoErrors())
// modelにセットされた値の検証
.andExpect(model().attribute("status", "OK"))
// 表示されるhtml(view)の検証
.andExpect(view().name("test"));
}
}- MockMvcを用いることでサーバ上にデプロイすることなくSpring MVCの動作を再現できます。動作オプションにstandaloneを指定することでSpring MVC のフレームワーク機能を利用しつつ、Controllerのテストを単体テスト観点で行なうことができます。詳細はこちらを参照ください。
- ExceptionHandlerが実装されたErrorHandlerControllerをControllerAdviceに設定するために、setControllerAdviceメソッドを使用します。
- @TestExecutionListenersに以下を指定することでテスト実行時にSpringのDIコンテナのライフサイクル管理機能を有効にしたうえでDIを利用できます。
DependencyInjectionTestExecutionListener.class
DirtiesContextTestExecutionListener.class
以上で全ての手順は完了になります