Spring Bootで MockMvcを使う際に、@RequestParamでLocalDateTimeを受け取る

## やりたかったこと

Spring SecurityでOAuth2認証を有効にしたRestControllerにおいて、引数にLocalDateTimeを取るAPIを作成。

そしてこれをMockMvcでテストしたい。

### プロダクションコード

```java:TestController.java
  @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``で初期化を実施。

```java:TestControllerTest.java
  
  @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());
  }
```

### 実行してみた結果

```console
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の指定クラス

日付などの設定をするために利用しているのでこれを使用。

[WebConversionService](https://spring.pleiades.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/format/WebConversionService.html)

DIする前提なので、スーパークラスである``FormattingConversionService``を指定した方がもしかしたら確実かもしれない。

### MessageConverterの指定クラス

Jackson用のが見つかったのでそれをそのまま使用。

[MappingJackson2HttpMessageConverter](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.html)

## 修正後コード

MockMvcの初期化をガッツリ変更して対応。

```java
  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マジで全然わからん