やりたかったこと
Spring SecurityでOAuth2認証を有効にしたRestControllerにおいて、引数にLocalDateTimeを取るAPIを作成。
そしてこれをMockMvcでテストしたい。
プロダクションコード
@GetMapping("/test/date")
public ResponseEntity<?> paramCheck(
@RequestParam("date") LocalDateTime date,
@AuthenticationPrincipal DefaultOAuth2User defaultOAuth2User) {
// 引数があればOK, なければBAD REQUEST
if (date != null) {
return new ResponseEntity<HealthCheckResponse>(
HealthCheckResponse.builder()
.randomId(UUID.randomUUID().toString())
.nowDate(date)
.build(),
HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
テストコード(修正前)
パラメータに渡すDefaultOAuth2User
を上書きするため、standaloneSetup
で初期化を実施。
@Autowired
private TestController targetController;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc =
MockMvcBuilders
.standaloneSetup(targetController)
.alwaysDo(print())
// OAuthの認証情報を更新するための処理を追加
.setCustomArgumentResolvers(new HandlerMethodArgumentResolver() {〜})
.build();
// Mock
MockitoAnnotations.openMocks(this);
}
@Test
void callTest() {
var testDate = LocalDateTime.of(2021, 03, 03, 12, 13, 14);
var sendDate = testDate.toString();
mockMvc
.perform(
get("/test/date")
.contentType(MediaType.APPLICATION_JSON)
.param("date", sendDate)
.characterEncoding("UTF-8")
.andExpect(status().isOk());
}
実行してみた結果
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
どうやら、String -> LocalDateTimeの変換で失敗していた模様。
何が起きたのか・・・?
bootRun
で起動した時にはデフォルトの設定がロードされて正常に変換されているので、MockMvcの問題であることは確実。
LocalDateTimeを引数に取る場合、内部的にSpringさんがよしなにして変換していると考えられるのだが、
MockMvcをstandaloneモードで初期化していたため、Spring Bootで自動設定している内容が反映されていないと仮定。
確かに、controllerAdviceも自分で指定しないといけないので、
この辺りの設定は全て手動でやる必要がありそう。
やるべきこと
- Requestの処理を
bootRun
の時に合わせたConfigurationを追加する - Responseの処理でJacksonを使用するため、その設定を反映する
調べてなんとなくわかったこと
MockMvcの初期化で、下記をそれぞれ指定すれば良さげ。
- setConversionService(リクエスト処理)
- setMessageConverters(レスポンス処理)
それぞれに導入する値はSpringBootがComponentとして管理しているので、Autowiredすれば自動で注入される(と仮定して実装)
ConversionServiceの指定クラス
日付などの設定をするために利用しているのでこれを使用。
DIする前提なので、スーパークラスであるFormattingConversionService
を指定した方がもしかしたら確実かもしれない。
MessageConverterの指定クラス
Jackson用のが見つかったのでそれをそのまま使用。
MappingJackson2HttpMessageConverter
修正後コード
MockMvcの初期化をガッツリ変更して対応。
private MockMvc mockMvc;
@BeforeEach
void setUp(
/** リクエストパラメータを処理するための共通サービス */
@Autowired WebConversionService conversionService,
/** Responseの処理をするためのコンバーター */
@Autowired MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
mockMvc =
MockMvcBuilders.standaloneSetup(targetController)
.alwaysDo(print())
// 認証情報の上書き(処理省略)
.setCustomArgumentResolvers(new HandlerMethodArgumentResolver() {〜})
// パラメータの処理で共通処理を利用するようにする
.setConversionService(conversionService)
// レスポンス処理でJacksonを使うようにする
.setMessageConverters(mappingJackson2HttpMessageConverter)
// エラーハンドラーの有効化(自前のAdviceがあるなら、足さないとスルーされる)
.setControllerAdvice(new CommonHandlerAdvice())
.build();
// Mock
MockitoAnnotations.openMocks(this);
}
実施したらうまくいったのでヨシ!
さいごに
SpringBootマジで全然わからん