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
相关产品推荐
相关产品推荐

