daisuzz.log

Clockを利用してテストがしやすいコードを書く

Java 8から標準ライブラリとして提供されているClockというクラスを利用して、現在時刻を取得することができます。

Clock (Java Platform SE 8)

現在時刻を取得するためにSystem.currentTimeMills()LocalDateTime.now()などのstatic methodを呼び出して利用する形が多いと思います。 ただ、static methodを利用して現在時刻を取得してしまうと、テストで利用する際に時刻のスタブを用意しづらいという問題があります。

Clockクラスは、static methodではなくインスタンスのメソッドを使って現在時刻を取得するため、テストにおいてスタブを用意しやすいというメリットがあります。 また、スタブとして特定のInstantを返すfixedというメソッドが用意されているため、スタブの設定も簡単におこなうことができます。

@Test
void testFixed() {

    final Clock c = Clock.fixed(Instant.parse("2021-01-01T00:00:00Z"), ZoneId.systemDefault());

    final Instant actual = c.instant();
    assertEquals(Instant.parse("2021-01-01T00:00:00Z"), actual);
 }

例えば、SpringでClockを利用する際には、以下のようにBeanとしてClockインスタンスを定義しておき、

@Bean
public Clock clock(){
  return Clock.system(ZoneId.of("Asia/Tokyo"));
}

プロダクションコードではコンストラクタインジェクションでDIをおこなって利用し、

@Component
class SampleBean {

  private final Clock clock;
  
  public void sample(){
    
    // ...
    
    Instant instant = clock.instant();
    
    // ...
  }
}

テストコードでは@MockBeanとMockitoを利用して固定のInstantを返す、といった形で使います。

class SampleBeanTest {

  @MockBean
  Clock clock;
  
  @InjectMocks
  SampleBean sampleBean;
  
  @Test
  public testSample(){
  
    when(clock.instant()).thenReturn(Instant.parse("2021-01-01T00:00:00Z"))
  
    // ...
  }
}

ちなみにいろいろな日付/時間クラスのnow()メソッドの裏側では、Clockインスタンスinstant()メソッドが利用されています。

jdk/LocalDateTime.java at 48d8650ae187821d0e79f7353c2f039518e313b1 · openjdk/jdk · GitHub

jdk/LocalDate.java at 48d8650ae187821d0e79f7353c2f039518e313b1 · openjdk/jdk · GitHub

jdk/Instant.java at 48d8650ae187821d0e79f7353c2f039518e313b1 · openjdk/jdk · GitHub