1. 概要

このクイックチュートリアルでは、テストのシステム時間をオーバーライドするさまざまな方法に焦点を当てます。

コードに現在の日付の周りにロジックがある場合があります。 new Date() Calendar.getInstance()などの関数呼び出しは、最終的にSystem.CurrentTimeMillisを呼び出す可能性があります。

Java Clock の使用方法の概要については、こちらの記事を参照してください。 または、AspectJを使用するには、ここ

2. Clock in java.timeを使用する

Java8のjava.timeパッケージには、必要に応じて代替クロックを接続できるようにするための抽象クラスjava.time.Clockが含まれています。 これにより、独自の実装をプラグインするか、ニーズを満たすためにすでに作成されている実装を見つけることができます。

私たちの目標を達成するために、上記のライブラリには、特別な実装を生成するための静的メソッドが含まれています。 不変でスレッドセーフでシリアル化可能な実装を返す2つを使用します。

最初のものは固定です。 それから、常に同じ インスタントを返すクロックを取得できます。これにより、テストが現在のクロックに依存しないことが保証されます。

これを使用するには、InstantZoneOffsetが必要です。

Instant.now(Clock.fixed( 
  Instant.parse("2018-08-22T10:00:00Z"),
  ZoneOffset.UTC))

2番目の静的メソッドはoffsetです。 この時計では、時計が別の時計をラップして、返されたオブジェクトが指定された期間だけ遅いまたは早いインスタントを取得できるようにします。

つまり、将来、過去、または任意の時点での実行をシミュレートすることができます

Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault());

// go to the future:
Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10));
        
// rewind back with a negative value:
clock = Clock.offset(constantClock, Duration.ofSeconds(-5));
 
// the 0 duration returns to the same clock:
clock = Clock.offset(constClock, Duration.ZERO);

duration クラスを使用すると、ナノ秒から数日までの操作が可能です。 また、期間を無効にすることもできます。これは、長さを無効にしてこの期間のコピーを取得することを意味します。

3. アスペクト指向プログラミングの使用

システム時間を上書きする別の方法は、AOPによるものです。 このアプローチでは、 Systemクラスを織り込んで、テストケース内で設定できる事前定義された値を返すことができます。

また、アプリケーションクラスを織り込んで、 System.currentTimeMillis()または new Date()への呼び出しを独自の別のユーティリティクラスにリダイレクトすることもできます。

これを実装する1つの方法は、AspectJを使用することです。

public aspect ChangeCallsToCurrentTimeInMillisMethod {
    long around(): 
      call(public static native long java.lang.System.currentTimeMillis()) 
        && within(user.code.base.pckg.*) {
          return 0;
      }
}

上記の例では、指定されたパッケージ内のSystem.currentTimeMillis()へのすべての呼び出しをキャッチしています。この場合はuser.code.base.pckg。*そしてこのイベントが発生するたびにゼロを返します

この場所で、ミリ秒単位で目的の時間を取得するための独自の実装を宣言できます。

AspectJを使用する利点の1つは、バイトコードレベルで直接動作するため、元のソースコードが機能する必要がないことです。

そのため、再コンパイルする必要はありません。

4. Instant.now()メソッドのモック

Instant クラスを使用して、タイムライン上の瞬間的なポイントを表すことができます。 通常、これを使用して、アプリケーションでイベントのタイムスタンプを記録できます。 このクラスのnow()メソッドを使用すると、UTCタイムゾーンのシステムクロックから現在の瞬間を取得できます。

テストするときにその動作を変更するためのいくつかの代替案を見てみましょう。

4.1. now()Clockでオーバーロード

now()メソッドを固定のClockインスタンスでオーバーロードできます。 java.timeパッケージのクラスの多くには、Clockパラメーターを受け取るnow()メソッドがあります。これにより、これが推奨されるアプローチになります。

@Test
public void givenFixedClock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-22T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));

    Instant instant = Instant.now(clock);

    assertThat(instant.toString()).isEqualTo(instantExpected);
}

4.2. Mockitoを使用する

さらに、パラメーターを送信せずに now()メソッドの動作を変更する必要がある場合は、Mockitoを使用できます。

@Test
public void givenInstantMock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-22T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
    Instant instant = Instant.now(clock);

    try (MockedStatic<Instant> mockedStatic = mockStatic(Instant.class)) {
        mockedStatic.when(Instant::now).thenReturn(instant);
        Instant now = Instant.now();
        assertThat(now.toString()).isEqualTo(instantExpected);
    }
}

4.3. 使用する JMockit

または、JMockitライブラリを使用することもできます。

JMockit は、静的メソッドモックする2つの方法を提供します。 1つは、MockUpクラスを使用しています。

@Test
public void givenInstantWithJMock_whenNow_thenGetFixedInstant() {
    String instantExpected = "2014-12-21T10:15:30Z";
    Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC"));
    new MockUp<Instant>() {
        @Mock
        public Instant now() {
            return Instant.now(clock);
        }
    };

    Instant now = Instant.now();

    assertThat(now.toString()).isEqualTo(instantExpected);
}

もう1つは、Expectationsクラスを使用することです。

@Test
public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC"));
    Instant instantExpected = Instant.now(clock);
    new Expectations(Instant.class) {
        {
            Instant.now();
            result = instantExpected;
        }
    };

    Instant now = Instant.now();

    assertThat(now).isEqualTo(instantExpected);
}

5. LocalDateTime.now()メソッドのモック

java.time パッケージのもう1つの便利なクラスは、LocalDateTimeクラスです。 このクラスは、ISO-8601カレンダーシステムでタイムゾーンのない日時を表します。 このクラスのnow()メソッドを使用すると、デフォルトのタイムゾーンでシステムクロックから現在の日時を取得できます。

以前に見たのと同じ代替手段を使用して、それをモックすることができます。 たとえば、 now()を固定のClockでオーバーロードします。

@Test
public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() {
    Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC"));
    String dateTimeExpected = "2014-12-22T10:15:30";

    LocalDateTime dateTime = LocalDateTime.now(clock);

    assertThat(dateTime).isEqualTo(dateTimeExpected);
}

6. 結論

この記事では、テストのシステム時間をオーバーライドするさまざまな方法について説明しました。 まず、ネイティブパッケージjava.timeとそのClockクラスを確認しました。 次に、アスペクトを適用してSystemクラスを織り込む方法を確認しました。 最後に、 InstantクラスとLocalDateTimeクラスでnow()メソッドをモックするためのさまざまな代替案を見ました。

いつものように、コードサンプルはGitHubにあります。