NestJS中Jest e2e测试并行执行失败 单独运行正常如何解决
问题根因
你判断的方向完全正确,Jest 默认会根据机器 CPU 核心数并行启动多个 worker 进程执行不同测试文件,你现在的随机报错核心是两个问题叠加:
- 所有测试套件共用同一个本地 MySQL 的
test数据库,并行运行时多个测试进程会同时读写、删除同一份数据 - 测试数据全部硬编码了固定 ID(比如你代码里反复操作 ID 为 1、2、99 的相机数据),不同套件的操作会互相干扰:比如 A 用例刚插入 ID=1 的相机准备断言删除结果,B 用例的清理逻辑刚好把这条数据删掉,A 的断言就会失败。因为每次测试的进程调度顺序不固定,所以每次失败的用例都不一样。
另外你现有测试代码还有两个隐患:一是afterEach只清理 ID 为 1、2 的数据,其他测试生成的残留数据会慢慢污染测试库,跑的次数越多越容易出奇怪问题;二是每个测试文件独立创建 Nest 应用、新建数据库连接,并行跑的时候很容易打满 MySQL 的最大连接数,触发连接超时类的报错。
修复方案
按优先级从高到低选择:
方案1:彻底做测试环境隔离(长期维护首选)
这是最稳妥的方案,一劳永逸解决所有冲突问题:
- 不要所有测试套件共用同一个固定的测试库:要么在每个测试套件启动时创建带随机后缀的独立数据库,跑完所有用例后自动删除;要么直接换 SQLite 内存数据库做 e2e 测试,启动速度快,完全不会有跨进程的资源冲突。
- 测试流程里不要硬编码资源 ID:新建相机的 POST 请求跑完后,从响应体里拿到数据库实际生成的自增 ID,存在局部变量里再拼接后续的删除、查询请求路径,不要直接写死
/components/cameras/1这种路径,从根源上避免数据撞车。 - 每个测试套件启动时先全量清空业务表的残留数据,不要只在
afterEach里删固定 ID 的记录。基于 TypeOrm 的清表逻辑可以参考:
beforeAll(async () => { // 保留你原有的模块编译、app初始化逻辑 const moduleFixture: TestingModule = await Test.createTestingModule({ // 你原来的imports、providers配置不变 }).compile(); app = moduleFixture.createNestApplication(); await app.init(); // 新增:初始化前清空所有业务表数据 const dataSource = app.get(DataSource); for (const entityMeta of dataSource.entityMetadatas) { const repo = dataSource.getRepository(entityMeta.name); await repo.clear(); } })
方案2:临时关闭Jest并行执行(快速验证问题用)
如果想最快确认是不是并行冲突导致的问题,可以直接修改package.json里的test:e2e脚本,加上--runInBand参数让 Jest 串行执行所有测试文件,改完之后脚本示例:
"test:e2e": "jest --config ./test/jest-e2e.json --runInBand"
这个方案改造成本最低,但是测试执行速度会明显变慢,而且硬编码 ID、数据残留的问题没有彻底解决,后续加新用例还是容易出问题,只适合临时排查用。
方案3:用事务回滚隔离用例数据
如果一定要用同一个 MySQL 库跑所有测试,可以给每个测试用例加事务包裹:用例执行前手动开启数据库事务,用例跑完不管成功失败都直接回滚事务,所有写操作都不会真正持久化到数据库,不同用例、不同套件之间不会互相干扰。要注意如果你的业务代码里本身用了显式事务,需要调整事务传播配置,避免嵌套事务冲突。
内容的提问来源于stack exchange,提问作者Sean Ghaeli
相关产品推荐
相关产品推荐

