单元测试中如何匹配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
相关产品推荐
相关产品推荐

