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

单元测试中如何解决未保存的瞬时实例异常问题

问题原因分析
  • Mock的UserRepository未实际持久化User对象:你用@Mock创建的UserRepository是Mockito模拟对象,调用save方法仅返回预设对象,并未真正将User存入测试数据库。此时User的id仍为null,属于瞬态对象。当保存关联该瞬态User的EmailVerificationToken时,JPA/Hibernate检测到关联对象未被持久化,触发TransientPropertyValueException。
  • 测试环境配置冲突:@DataJpaTest会自动配置嵌入式数据库、真实JPA Repository等测试环境,Mock UserRepository完全多余,还会覆盖真实的UserRepository,导致无法完成持久化操作。
优化方案
  1. 移除Mock的UserRepository:使用@DataJpaTest自动配置的真实UserRepository,将User存入测试数据库,确保其成为持久化状态。
  2. 修正setUp逻辑:先通过真实UserRepository保存User,获取带有效id的持久化对象,再关联到EmailVerificationToken后保存。
  3. 统一测试数据:testFindByUser使用setUp中创建的持久化User对象,避免因新对象无id导致匹配失败。
修改后的完整测试代码
@DataJpaTest(properties = {
        "spring.jpa.properties.javax.persistence.validation.mode=none"
})
public class EmailVerificationTokenRepositoryTest {

    @Autowired
    private EmailVerificationTokenRepository repository;

    @Autowired
    private UserRepository userRepository; // 使用真实Repository

    private Users testUser; // 共享测试用User对象

    @BeforeEach
    public void setUp() {
        Set<String> role = new HashSet<>();
        role.add("ROLE_USER");
        Users user = Users.builder()
                .email("david@gmail.com")
                .password("1234")
                .isEnabled(true)
                .isNonLocked(true)
                .roles(role)
                .createdAt(LocalDateTime.now())
                .build();
        // 真实保存User到测试数据库,得到持久化对象
        testUser = userRepository.save(user);

        EmailVerificationToken evt = EmailVerificationToken.builder()
                .createdAt(LocalDateTime.now())
                .expiresAt(LocalDateTime.now().plusHours(4))
                .token("token")
                .user(testUser)
                .build();
        repository.save(evt);
    }

    @AfterEach
    public void tearDown() {
        repository.deleteAll();
        userRepository.deleteAll(); // 同步清理User表数据
    }

    @Test
    @DisplayName("根据Token查找邮箱验证对象")
    public void testFindByToken() {
        Optional<EmailVerificationToken> result = repository.findByToken("token");
        assertTrue(result.isPresent());
        assertEquals(testUser.getId(), result.get().getUser().getId());
    }

    @Test
    @DisplayName("根据User查找邮箱验证对象")
    public void testFindByUser() {
        Optional<EmailVerificationToken> result = repository.findByUser(testUser);
        assertTrue(result.isPresent());
        assertEquals("token", result.get().getToken());
    }
}
额外说明
  • @DataJpaTest默认会为每个测试方法创建独立事务,测试结束自动回滚,tearDown方法可选择保留或移除,手动清理能避免测试间数据干扰。
  • 测试findByUser时必须使用带有效id的持久化User对象,新构建的无id对象无法匹配数据库中的关联数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 05:55:08