JUnit批量运行时findById测试失败,单独运行正常的问题排查
问题分析:JPA仓库测试类批量运行时单例测试失败的原因
测试基于JpaRepository实现的User仓库层时,编写了JUnit测试类,逐个运行测试用例全部通过,但运行整个测试类时,
testFindById_Success用例总是失败,提示找不到对应ID为1L的数据。测试代码如下:
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.runner.RunWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith({MockitoExtension.class, SpringExtension.class}) @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class UserRepositoryTest { @Autowired private UserRepository userRepository; @BeforeEach void initUseCase() { User user1 = new User(null, "First N1", "Last N1", "first@gmail.com", "password", UserRole.USER, null, false, true); User user2 = new User(null, "First N2", "Last N2", "second@gmail.com", "password", UserRole.USER, null, false, true); User user3 = new User(null, "First N3", "Last N3", "third@gmail.com", "password", UserRole.USER, null, false, true); List<User> users = Arrays.asList(user1, user2, user3); userRepository.saveAllAndFlush(users); } @AfterEach public void destroyAll(){ userRepository.flush(); userRepository.deleteAll(); } // READ @Test @Transactional void testFindById_Success() { assertTrue(userRepository.findById(1L).isPresent()); assertEquals("First N1", userRepository.findById(1L).get().getFirstName()); } @Test @Transactional void testFindAll_Success() { assertEquals(3, this.userRepository.findAll().size()); } // CREATE @Test @Transactional void testCreateUser_Success() { User user = new User(4L, "First N4", "Last N4", "forth@gmail.com", "password", UserRole.USER, null, true, true); userRepository.save(user); assertEquals(4, userRepository.findAll().size()); } }
核心原因
- 数据库自增ID未重置,硬编码ID断言失效:
批量运行测试时,每个测试方法都会执行@BeforeEach插入3条数据,但数据库的自增序列不会因@AfterEach的删除操作重置。比如先运行testCreateUser_Success时,会插入3条初始数据+1条新数据,自增ID会走到4;删除数据后,自增序列的下一个值仍是5。当testFindById_Success运行时,新插入的3条数据ID会是5、6、7,代码里硬编码查找1L自然找不到。 - 冗余注解导致测试逻辑冲突:
同时混用@RunWith(SpringRunner.class)和@ExtendWith(SpringExtension.class)属于冗余配置,@DataJpaTest已经默认集成Spring测试扩展;额外添加的MockitoExtension未被使用,反而可能干扰测试上下文。另外@DataJpaTest默认给每个测试方法添加事务并自动回滚,手动添加的@Transactional属于冗余配置,可能和默认事务逻辑冲突。 @AfterEach清理逻辑无效:deleteAll()仅删除数据,不会重置自增序列;手动调用的flush()也是多余操作,deleteAll()本身会触发数据刷新。
修复方案
移除硬编码ID,改用动态获取的ID断言:
不要依赖固定ID,而是从插入的数据中获取实际生成的ID进行断言,比如修改testFindById_Success:private Long user1Id; @BeforeEach void initUseCase() { // 保存第一条用户并记录ID User user1 = new User(null, "First N1", "Last N1", "first@gmail.com", "password", UserRole.USER, null, false, true); User savedUser1 = userRepository.save(user1); user1Id = savedUser1.getId(); // 保存另外两个用户 User user2 = new User(null, "First N2", "Last N2", "second@gmail.com", "password", UserRole.USER, null, false, true); User user3 = new User(null, "First N3", "Last N3", "third@gmail.com", "password", UserRole.USER, null, false, true); userRepository.saveAll(Arrays.asList(user2, user3)); } @Test void testFindById_Success() { assertTrue(userRepository.findById(user1Id).isPresent()); assertEquals("First N1", userRepository.findById(user1Id).get().getFirstName()); }清理冗余注解,简化测试配置:
移除不必要的注解,保留@DataJpaTest和@AutoConfigureTestDatabase即可:@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class UserRepositoryTest { // ... 测试代码 }优化数据清理逻辑(可选):
@DataJpaTest默认会回滚事务,@AfterEach的deleteAll()可以直接移除。如果使用不支持事务回滚的数据库,可在@BeforeEach开头先清空数据,避免残留:@BeforeEach void initUseCase() { userRepository.deleteAll(); // 先清空数据 // 插入测试数据 }
内容的提问来源于stack exchange,提问作者Deyvid Dimitrov
相关产品推荐
相关产品推荐

