Appearance
バリデーションエラーのハンドリング
実装
pom.xml
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>controller/AuthController.kt
kotlin
package nob.example.easyapp.controller
import jakarta.validation.Valid
import nob.example.easyapp.controller.model.LoginRequest
import nob.example.easyapp.controller.model.LoginResponse
import nob.example.easyapp.controller.model.MeRequest
import nob.example.easyapp.controller.model.MeResponse
import nob.example.easyapp.service.AuthService
import nob.example.easyapp.service.model.LoginInModel
import nob.example.easyapp.service.model.MeInModel
import org.springframework.web.bind.annotation.*
/**
* 認証コントローラーです。
*/
@RestController
@RequestMapping("/api/v1")
class AuthController(private val authService: AuthService) {
/**
* 認証処理を呼び出します。
*/
@PostMapping("/login")
fun login(@RequestBody @Valid req: LoginRequest): LoginResponse {
return LoginResponse(authService.login(LoginInModel(req.name, req.password)).valid)
}
}controller/model/AuthModel.kt
kotlin
package nob.example.easyapp.controller.model
import jakarta.validation.constraints.NotBlank
/**
* 認証向けのリクエストモデルです。
*/
data class LoginRequest(
/**
* ユーザ名
*/
@NotBlank(message = "{loginRequest.name.NotBlank}")
val name: String,
/**
* パスワード
*/
val password: String
)
/**
* 認証向けのレスポンスモデルです。
*/
data class LoginResponse(
/**
* 認証可否
*/
val valid: Boolean
)resources/ValidationMessages.properties
shell
# エラーメッセージの辞書ファイルです。
loginRequest.name = ユーザ名
loginRequest.name.NotBlank = {loginRequest.name}を入力してください。handler/MethodArgumentNotValidExceptionHandler.kt
kotlin
package nob.example.easyapp.handler
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
/**
* バリデーションによる例外のハンドラです。
*/
@RestControllerAdvice
class MethodArgumentNotValidExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodArgumentNotValidException(e: MethodArgumentNotValidException): ResponseEntity<MethodArgumentNotValidExceptionResponse> {
val messageList: List<String?> = e.allErrors.map { it.defaultMessage }
return ResponseEntity(MethodArgumentNotValidExceptionResponse(messageList), HttpStatus.BAD_REQUEST)
}
/**
* MethodArgumentNotValidException発生時のレスポンスボディです。
*/
data class MethodArgumentNotValidExceptionResponse(
/**
* エラーメッセージのリスト
*/
val messageList: List<String?>
)
}テスト
AuthControllerTest.kt
kotlin
package nob.example.easyapp.controller
import nob.example.easyapp.controller.model.LoginRequest
import nob.example.easyapp.handler.MethodArgumentNotValidExceptionHandler
import nob.example.easyapp.service.AuthService
import nob.example.easyapp.service.model.LoginInModel
import nob.example.easyapp.service.model.LoginOutModel
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.context.bean.override.mockito.MockitoBean
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import tools.jackson.databind.ObjectMapper
/**
* AuthControllerのテストクラスです。
*/
@WebMvcTest(AuthController::class)
class AuthControllerTest {
@Autowired
lateinit var mockMvc: MockMvc
@MockitoBean
lateinit var authService: AuthService
var objectMapper: ObjectMapper = ObjectMapper()
@Test
fun testLoginInvalidName() {
// リクエスト作成
val req = LoginRequest("", "passwd")
// serviceのモック化
whenever(authService.login(LoginInModel(req.name, req.password))).thenReturn(LoginOutModel(true))
// API呼び出し
val result = mockMvc.perform(
post("/api/v1/login")
.content(objectMapper.writeValueAsString(req))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isBadRequest())
.andReturn()
// 結果の検証
assertThat(result.response.contentAsString).isEqualTo(
objectMapper.writeValueAsString(
MethodArgumentNotValidExceptionHandler.MethodArgumentNotValidExceptionResponse(listOf("ユーザ名を入力してください。"))
)
)
}
}API動確
$ curl -X POST -H 'Content-Type: application/json' -d '{"name": "", "password": "passwd"}' localhost:8080/api/v1/login
{"messageList":["ユーザ名を入力してください。"]}