单元测试中如何解决未保存的瞬时实例异常问题
问题原因分析
- Mock的UserRepository未实际持久化User对象:你用
@Mock创建的UserRepository是Mockito模拟对象,调用save方法仅返回预设对象,并未真正将User存入测试数据库。此时User的id仍为null,属于瞬态对象。当保存关联该瞬态User的EmailVerificationToken时,JPA/Hibernate检测到关联对象未被持久化,触发TransientPropertyValueException。 - 测试环境配置冲突:
@DataJpaTest会自动配置嵌入式数据库、真实JPA Repository等测试环境,Mock UserRepository完全多余,还会覆盖真实的UserRepository,导致无法完成持久化操作。
优化方案
- 移除Mock的UserRepository:使用
@DataJpaTest自动配置的真实UserRepository,将User存入测试数据库,确保其成为持久化状态。 - 修正setUp逻辑:先通过真实UserRepository保存User,获取带有效id的持久化对象,再关联到EmailVerificationToken后保存。
- 统一测试数据:
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
相关产品推荐
相关产品推荐

