1. ホーム
  2. java

Java 8 Clockを使用したクラスのユニットテスト

2023-10-13 11:14:50

質問

Java 8 の導入 java.time.Clock の引数として使用することができます。 java.time オブジェクトの引数として使用することができ、本物の時計や偽物の時計を注入することができます。例えば、私はあなたが Clock.fixed() を作成し、次に Instant.now(clock) を呼び出すと、固定された Instant を返します。これはユニットテストに最適ですね。

しかし、私はこれをどのように使用するのが最善かについて困っています。私は、以下のようなクラスを持っています。

public class MyClass {
    private Clock clock = Clock.systemUTC();

    public void method1() {
        Instant now = Instant.now(clock);
        // Do something with 'now'
    }
}

さて、このコードをユニットテストしたいと思います。私は clock を固定時間生成するように設定できるようにする必要があります。 method() を異なる時刻でテストできるようにするためです。明らかに、私はリフレクションを使って clock メンバーを特定の値に設定するためにリフレクションを使うこともできますが、 リフレクションに頼る必要がないのはいいことです。私はパブリックな setClock() メソッドを作ることもできるが、それは間違っていると感じる。私は Clock 引数を加えたくないのです。実際のコードではクロックを渡すことにこだわるべきではありませんから。

これを処理するための最良のアプローチは何でしょうか?これは新しいコードなので、私はクラスを再編成することができました。

編集: 明確にするために、私は単一の MyClass オブジェクトを構築し、その1つのオブジェクトに2つの異なるクロック値を表示させることができる必要があります (あたかも通常のシステムクロックが動いているかのように)。そのため、私はコンストラクターに固定クロックを渡すことができません。

どのように解決するのですか?

Jon Skeetの答えとコメントをコードにしてみましょう。

テスト中のクラスです。

public class Foo {
    private final Clock clock;
    public Foo(Clock clock) {
        this.clock = clock;
    }

    public void someMethod() {
        Instant now = clock.instant();   // this is changed to make test easier
        System.out.println(now);   // Do something with 'now'
    }
}

ユニットテストです。

public class FooTest() {

    private Foo foo;
    private Clock mock;

    @Before
    public void setUp() {
        mock = mock(Clock.class);
        foo = new Foo(mock);
    }

    @Test
    public void ensureDifferentValuesWhenMockIsCalled() {
        Instant first = Instant.now();                  // e.g. 12:00:00
        Instant second = first.plusSeconds(1);          // 12:00:01
        Instant thirdAndAfter = second.plusSeconds(1);  // 12:00:02

        when(mock.instant()).thenReturn(first, second, thirdAndAfter);

        foo.someMethod();   // string of first
        foo.someMethod();   // string of second
        foo.someMethod();   // string of thirdAndAfter 
        foo.someMethod();   // string of thirdAndAfter 
    }
}