« 2021年8月 | トップページ | 2022年8月 »

2022年2月22日 (火)

Dateをmockするメモ

0. 時刻が絡むとめんどくさい

使い古された内容だけに、多分に自分用メモ。

よくこんなコードを書かないだろうか。僕はとってもよく書く。

  Date date1 = new Date();
  // あるいは
  Date date2 = Date.from(Instant.now());
        

よくある「現在時刻が必要な処理」で出てくるやつである。

プロダクションコードだけを考えるなら、まったくこれで問題はないと思うんだけど、ちゃんと時刻が利用できているかどうかをユニットテストで確認しようとすると、これでは途端に問題になる。

こういう場合Date部分を生成部分を固定の値を返すようなモックに置き換えることができれば、安定してテストが成立しうるので、その方法を調べることに。

1. mockしてやる

ググってみると、Overriding System Time for Testing in Javaという、安定のbaeldungさんの記事がヒット。

要はInstant.now()をモックすることで対応可能とのこと。

「そういやspring-boot-starter-testにはmockito含まれてたよな?」と思い出したので確認してみる。

  $ ./gradlew dependencies
  \--- org.springframework.boot:spring-boot-starter-test -> 2.6.3
  +--- org.mockito:mockito-core:4.0.0
  +--- org.mockito:mockito-junit-jupiter:4.0.0
  |    +--- org.mockito:mockito-core:4.0.0 (*)
        

うむ、間違いなし。PoCプロジェクトにしたSpring Boot 2.6.3では、mockito-core 4.0.0が入っているようだ。

これを利用して、固定の時刻を返すDateを作ってみる。

プロダクションコード
  @NoArgsConstructor
  @AllArgsConstructor
  @Getter
  @Setter
  public class TimeNotification {
      @JsonProperty("current_time")
      @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Tokyo")
      private Date currentTime;
  }

  @RestController
  public class MainController {
      @GetMapping("/")
      public TimeNotification notifyTime() {
          return new TimeNotification(Date.from(Instant.now()));
      }
  }
テストコード
  @Test
  void index() throws Exception {
      // Instant側はZ時刻帯(=UTC)、ZoneInfoはJSTなので、9時間ずらす
      Clock clock = Clock.fixed(Instant.parse("2022-02-22T12:11:23Z"), ZoneId.of("Asia/Tokyo"));
      Instant instant = Instant.now(clock);

      try(MockedStatic mockedInstant = Mockito.mockStatic(Instant.class)) {
          mockedInstant.when(Instant::now).thenReturn(instant);

          mockMvc.perform(get("/"))
                  .andExpect(status().isOk())
                  .andExpect(content().json("{\"current_time\":\"2022-02-22 21:11:23\"}"))
                  .andReturn();
      }
  }

で、テストを実行してみたのだが、

org.mockito.exceptions.base.MockitoException: 
The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks

Mockito's inline mock maker supports static mocks based on the Instrumentation API.
You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'.
Note that Mockito's inline mock maker is not supported on Android.

...と怒られる。スタックトレースを読むと、スタティックメソッドのモッキングにはmockito-inlineというライブラリが必要な様子。

build.gradleに追加。

  dependencies {
      testImplementation "org.mockito:mockito-inline:4.0.0"
  }

これでようやくテストが通るようになった。

2. 本日のソースコード

全文はこちらをどうぞ。

« 2021年8月 | トップページ | 2022年8月 »

2023年12月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            

最近のトラックバック

無料ブログはココログ