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

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()本身会触发数据刷新。

修复方案

  1. 移除硬编码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());
    }
    
  2. 清理冗余注解,简化测试配置:
    移除不必要的注解,保留@DataJpaTest和@AutoConfigureTestDatabase即可:

    @DataJpaTest
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    class UserRepositoryTest {
        // ... 测试代码
    }
    
  3. 优化数据清理逻辑(可选):
    @DataJpaTest默认会回滚事务,@AfterEach的deleteAll()可以直接移除。如果使用不支持事务回滚的数据库,可在@BeforeEach开头先清空数据,避免残留:

    @BeforeEach
    void initUseCase() {
        userRepository.deleteAll(); // 先清空数据
        // 插入测试数据
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 01:10:32