概要
- 独自RuntimeExceptionを作る
- 独自例外はControllerAdviceで横断的にハンドリングする
- テストを書く
前提
- Kotlin
- RestControllerを想定
- ゆえにRestControllerAdviceを使う
- テストはSpock
独自例外の作成
- RuntimeExceptionを作成
- コンストラクタ引数はnullを許可したくなかったので、superのコンストラクタにそれぞれ渡している
class MyException : RuntimeException {
constructor(message: String, cause: Throwable) : super(message, cause)
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
constructor() : super()
}
ハンドラークラスの作成
- クラスに
@RestControllerAdvice
を付与
- ハンドルするメソッドに
@ExceptionHandler
を付与
- ハンドルするメソッドに
@ResponseStatus
を付与
- 指定したHTTPステータスがレスポンスに設定される
- この例だとINTERNAL_SERVER_ERROR(500)
- ハンドルするメソッドは対象の例外を引数で受け取る
- 返り値の型がレスポンスボディに設定される
- デフォルトだとRestControllerならJSONになると思う
@RestControllerAdvice
class MyExceptionHandler {
@ExceptionHandler(MyException::class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
fun handleMyException(e: MyException): Map<String, String> = mapOf("message" to "Error!!")
}
テストの作成
- クラスに
@SpringBootTest
と @AutoConfigureMockMvc
を付与
- スタブコントローラを作成する
- CotrollerAdviceはコントローラに横断的に適用されるので、個別のコントローラに対しては(ユニット)テストしない
- 常にテスト対象の例外をthrowするようなダミーのエンドポイントを用意する
- テストクラスに
@ActiveProfiles
を、スタブに @Profile
を付与する
- 必須ではない
- スタブのエンドポイントが重複した場合を避けるため
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("myExceptionHandlerSpec")
class MyExceptionHandlerSpec extends Specification {
@Autowired
MockMvc mockMvc
def 'MyExceptionが発生した場合はHTTP:500になる'() {
expect:
mockMvc.perform(MockMvcRequestBuilders.get('/mock'))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.INTERNAL_SERVER_ERROR.value()))
.andExpect(MockMvcResultMatchers.jsonPath("\$.message").value("Error!!"))
}
}
@RestController
@RequestMapping("/mock")
@Profile("exceptionHandlerSpec")
class StubController {
@GetMapping
def test() {
throw new MyException()
}
}
参考