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

单元测试中如何匹配AuthenticationResponse动态生成的expiresAt字段断言?

解决动态生成时间字段的测试断言问题

这是测试动态时间字段时非常常见的坑,我给你几个实用的解决方案,你可以根据自己的场景选择:

方案1:逐个字段断言,对时间做范围校验

不要直接比较整个AuthenticationResponse对象,而是分别校验每个字段,对expiresAt只验证它是否在合理的时间范围内(比如预期时间的前后100毫秒内),这样既保证了逻辑正确,又容忍了代码执行的微小时间差。

修改你的测试代码:

@Test
public void loginTest_whenCredentialsAreCorrect() {
    // 准备loginRequest等逻辑...
    
    AuthenticationResponse actualResponse = authService.login(loginRequest);
    
    // 校验固定字段
    assertEquals("jack", actualResponse.getUserName());
    assertEquals("jwttest", actualResponse.getJwtToken());
    assertEquals("jwt1", actualResponse.getRefreshToken());
    
    // 校验expiresAt:应该是当前时间加900秒,允许100毫秒误差
    Instant expectedExpiryBase = Instant.now().plusMillis(900 * 1000);
    assertThat(actualResponse.getExpiresAt())
        .isBetween(expectedExpiryBase.minusMillis(100), expectedExpiryBase.plusMillis(100));
}

这种方案不需要修改业务代码,快速就能解决问题。

方案2:抽离时间生成逻辑并Mock(更严谨)

把AuthService里获取当前时间的逻辑抽成一个单独的方法,然后在测试中Mock这个方法,让它返回固定的时间,这样就能保证expiresAt完全匹配。

首先修改AuthService:

@Service
public class AuthService {
    // 抽离时间获取方法,方便测试Mock
    protected Instant getCurrentInstant() {
        return Instant.now();
    }

    public AuthenticationResponse login(LoginRequest loginRequest) {
        // 其他逻辑...
        Instant expiresAt = getCurrentInstant().plusMillis(900*1000);
        // 其他逻辑...
        return authenticationResponse;
    }
}

然后修改测试类,用Mockito部分MockAuthService:

@Spy // 使用Spy来部分Mock真实对象
@InjectMocks
private AuthService authService;

@Test
public void loginTest_whenCredentialsAreCorrect() {
    // 固定一个当前时间
    Instant fixedNow = Instant.parse("2020-07-30T13:40:45.828Z");
    // Mock getCurrentInstant方法,返回固定时间
    doReturn(fixedNow).when(authService).getCurrentInstant();
    
    // 构建预期响应,expiresAt用固定时间计算
    AuthenticationResponse expectedResponse = new AuthenticationResponse();
    expectedResponse.setJwtToken("jwttest");
    expectedResponse.setUserName("jack");
    expectedResponse.setRefreshToken("jwt1");
    expectedResponse.setExpiresAt(fixedNow.plusMillis(900000));
    
    AuthenticationResponse actualResponse = authService.login(loginRequest);
    // 现在可以直接断言整个对象相等
    assertEquals(expectedResponse, actualResponse);
}

这种方案适合需要精确控制时间的场景,测试逻辑更严谨,也方便后续测试过期时间相关的逻辑。

方案3:使用AssertJ的递归比较,自定义时间比较器

如果不想逐个字段写断言,可以用AssertJ的递归比较功能,给Instant类型自定义一个比较器,允许微小的时间差:

@Test
public void loginTest_whenCredentialsAreCorrect() {
    // 准备预期响应...
    AuthenticationResponse expectedResponse = new AuthenticationResponse();
    expectedResponse.setJwtToken("jwttest");
    expectedResponse.setUserName("jack");
    expectedResponse.setRefreshToken("jwt1");
    expectedResponse.setExpiresAt(Instant.now().plusMillis(900000));
    
    AuthenticationResponse actualResponse = authService.login(loginRequest);
    
    // 用递归比较,对Instant类型允许100毫秒内的差异
    assertThat(actualResponse)
        .usingRecursiveComparison()
        .withComparatorForType(
            (instant1, instant2) -> {
                long timeDiff = Math.abs(instant1.toEpochMilli() - instant2.toEpochMilli());
                return timeDiff <= 100 ? 0 : instant1.compareTo(instant2);
            },
            Instant.class
        )
        .isEqualTo(expectedResponse);
}

这种方案代码简洁,但需要你熟悉AssertJ的API,适合喜欢用断言库简化测试的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:57:55