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

NestJS+TypeORM单元测试事务回滚失效问题排查与修复

修复单元测试的数据库事务回滚问题

问题分析

你当前的代码里,UsersService 使用的是应用默认的 DataSource 连接,而你创建的 queryRunner 是独立的数据库连接实例——两者的操作不在同一个事务上下文里,所以调用 rollbackTransaction() 自然无法回滚 service 执行的数据库操作。

修复后的代码

import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { AppModule } from '../../app.module';
import { DataSource, QueryRunner, Repository } from 'typeorm';
import { DATA_SOURCE } from '../database/database.providers';
import { runSeeders } from 'typeorm-extension';
import { ConfigService } from '@nestjs/config';
// 替换为你实际的User实体类
import { User } from './entities/user.entity';

describe('UsersService', () => {
  let service: UsersService;
  let module: TestingModule;
  let dataSource: DataSource;
  let queryRunner: QueryRunner;
  let config: ConfigService;
  // 保存原始仓库引用,用于测试后恢复
  let originalUserRepo: Repository<User>;

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    config = module.get<ConfigService>(ConfigService);
    dataSource = module.get<DataSource>(DATA_SOURCE);

    if (config.get<string>('NODE_ENV') === 'testing') {
      console.info('Running seeders for testing environment');
      await runSeeders(dataSource);
    }

    service = module.get<UsersService>(UsersService);
    // 保存service原本依赖的仓库实例
    originalUserRepo = service.userRepository; // 需根据你的service实际属性名调整
  });

  beforeEach(async () => {
    queryRunner = dataSource.createQueryRunner();
    await queryRunner.startTransaction();
    // 将service的仓库替换为queryRunner事务内的实例,让操作纳入事务
    service.userRepository = queryRunner.manager.getRepository(User);
  });

  afterEach(async () => {
    // 回滚当前事务,清理测试数据
    await queryRunner.rollbackTransaction();
    await queryRunner.release();
    // 恢复service的原始仓库,避免测试间相互影响
    service.userRepository = originalUserRepo;
  });

  afterAll(async () => {
    await module.close();
  });

  it('should be defined', async () => {
    await service.create({ firstName: 'FN', lastName: 'LN', email: 'dan@example.com' });
    await service.create({ firstName: 'FN2', lastName: 'LN2', email: 'dan2@example.com' });

    expect(service).toBeDefined();
  });

  it('should return a user', async () => {
    // 注意:前一个测试的操作已被回滚,需重新创建测试数据
    await service.create({ firstName: 'FN', lastName: 'LN', email: 'dan@example.com' });
    const user = await service.findOneByEmail('dan@example.com');
    expect(user).toBeDefined();
  });
});

关键修复点

  • 绑定事务上下文:在 beforeEach 中将 UsersService 依赖的仓库替换为 queryRunner 实例的仓库,让所有业务操作都处于事务范围内。
  • 恢复原始依赖:afterEach 中恢复 service 的原始仓库,避免不同测试用例之间的交叉污染。
  • 保证测试独立性:每个测试用例需要独立准备数据,因为前一个测试的操作会被事务回滚,后续测试无法访问之前创建的数据。

备选方案(若service直接依赖DataSource)

如果你的 UsersService 是直接注入 DataSource 而非具体仓库,可以替换 service 的数据源为事务内的管理器:

// beforeEach内替换
service.dataSource = queryRunner.manager;
// afterEach内恢复
service.dataSource = dataSource;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 18:53:10