如何用java.time.Clock模拟时间的流逝?

How do I simulate the passage of time with java.time.Clock?

假设我有这个 class、ClassToBeTested,其中方法调用之间经过的时间很重要。 foo returns 一个不同的值,具体取决于您在过去 x 小时内是否打过电话 foo

我的实现方式是在调用foo的时候获取当前的Instant,和上次调用fooInstant进行比较,我存储在一个字段中。

由于ClassToBeTested取决于当前时间,所以我添加了一个Clock字段,调用者在创建ClassToBeTested时需要传入:

class ClassToBeTested {
    private final Clock clock;
    private Instant lastCalled;

    public ClassToBeTested(Clock clock) {
        this.clock = clock;
    }

    public String foo() {
        if (lastCalled == null || Duration.between(lastCalled, Instant.now(clock)).compareTo(Duration.ofHours(1)) >= 0) {
            lastCalled = Instant.now(clock);
            return "A";
        } else {
            lastCalled = Instant.now(clock);
            return "B";
        }
    }
}

不过,当我编写测试时,我意识到没有创建可变 Clock 的工厂方法。我尝试创建自己的可变时钟,以便我的测试看起来像这样:

private final FakeClock fakeClock = new FakeClock();

@Test
public void fooReturnsAAfterAnHour() {
    var myInstance = new ClassToTest(fakeClock);
    assertThat(myInstance.foo(), is("A"));
    fakeClock.goForward1Hour(); // this mutates the clock, and changes what instant() will return
    assertThat(myInstance.foo(), is("A"))
}

@Test
public void fooReturnsBWithinAnHour() {
    var myInstance = new ClassToTest(fakeClock);
    assertThat(myInstance.foo(), is("A"));
    fakeClock.goForward30Minutes(); // this mutates the clock, and changes what instant() will return
    assertThat(myInstance.foo(), is("B"))
}

才发现在文档中,它说:

All implementations that can be instantiated must be final, immutable and thread-safe.

所以 Clock 的可变实现似乎是不正确的。但是查看有关堆栈溢出的帖子,许多人建议使用可变时钟(example). Some also suggest mocking the Clock abstract class. When I tried that out, jMock (which is what I'm using) doesn't like it because Clock is not an interface. Apparently, if I want to mock classes, I'd have to include another dependency,它主要用于模拟遗留代码。对我来说,这听起来不适合模拟 Clock

我还可以为 ClassToBeTested 中的 clock 字段添加一个 setter,这样我就可以:

myInstance.setClock(Clock.offset(clock, Duration.ofHours(1)));

提前时间。但我认为这会破坏封装。

在这一点上,我没有主意了。我能做什么?

您的 FakeClock 方法很好。这就是我会使用的方法。

如果您更喜欢使用库中的实现而不是您自己的实现,则可以使用 MutableClock from the threeten-extra 库。 class 正是为这个用例设计的。

“不可变”要求来自 Clock class 中的 removed Java 17。它被删除不是因为对 [=12= 进行了更改] 的实施,但因为该要求是有害的限制(请参阅您的用例)而没有达到有用的目的。因此,如果您使用 Java <17,我会说忽略该要求是安全的。