You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用java.time.Clock模拟时间流逝完成时间依赖类的单元测试

时间依赖类的测试方案

方案1:测试专用可变FakeClock(最简便)

JDK文档中对Clock实现必须不可变、final、线程安全的要求,是面向通用公开Clock实现的约束,仅在测试代码中内部使用的可变FakeClock不需要遵守该规范。只要你明确该FakeClock仅用于单测场景,且单测执行时不存在多线程并发修改的情况,就可以直接使用,完全不会有问题。
你可以按如下方式实现FakeClock:

// 仅在测试代码中存在,不需要对外公开
public class FakeClock extends Clock {
    private Instant now;
    private final ZoneId zone;

    public FakeClock() {
        this(Instant.now(), ZoneId.systemDefault());
    }

    public FakeClock(Instant now, ZoneId zone) {
        this.now = now;
        this.zone = zone;
    }

    @Override
    public ZoneId getZone() {
        return zone;
    }

    @Override
    public Clock withZone(ZoneId zone) {
        return new FakeClock(now, zone);
    }

    @Override
    public Instant instant() {
        return now;
    }

    public void goForward(Duration duration) {
        this.now = this.now.plus(duration);
    }

    public void goForward1Hour() {
        goForward(Duration.ofHours(1));
    }

    public void goForward30Minutes() {
        goForward(Duration.ofMinutes(30));
    }
}

测试代码直接用你原来的写法即可,不需要修改生产代码,也不会引入额外依赖。

方案2:抽象自定义时间提供者接口(最符合规范)

如果你严格希望遵守JDK的Clock规范,同时解决jMock无法mock类的问题,可以在生产代码中额外抽象一个时间提供者接口:

// 生产代码中新增
public interface TimeProvider {
    Instant now();
}

// 生产环境默认实现
public class SystemTimeProvider implements TimeProvider {
    private final Clock clock;

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

    @Override
    public Instant now() {
        return Instant.now(clock);
    }
}

然后修改ClassToBeTested依赖该接口而非直接依赖Clock:

class ClassToBeTested {
    private final TimeProvider timeProvider;
    private Instant lastCalled;

    public ClassToBeTested(TimeProvider timeProvider) {
        this.timeProvider = timeProvider;
    }

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

测试时既可以自己实现可变的FakeTimeProvider,也可以直接用jMock mockTimeProvider接口,完全符合所有规范要求,也不会破坏封装性。

方案3:反射修改私有字段(无侵入生产代码)

如果你既不想用可变Clock,也不想修改生产代码结构,可以在测试中用反射修改ClassToBeTested的私有字段,配合JDK自带的不可变Clock实现测试:

@Test
public void fooReturnsAAfterAnHour() throws NoSuchFieldException, IllegalAccessException {
    Clock initialClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
    var myInstance = new ClassToBeTested(initialClock);
    assertThat(myInstance.foo(), is("A"));

    // 构造偏移1小时的不可变Clock
    Clock oneHourLaterClock = Clock.offset(initialClock, Duration.ofHours(1));
    // 反射修改clock字段
    Field clockField = ClassToBeTested.class.getDeclaredField("clock");
    clockField.setAccessible(true);
    clockField.set(myInstance, oneHourLaterClock);

    assertThat(myInstance.foo(), is("A"));
}

该方案完全不需要修改生产代码,也不会破坏生产代码的封装性,只是测试代码会多一点反射逻辑。

内容的提问来源于stack exchange,提问作者Sweeper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 12:06:03