使用QueryRunner事务的TypeORM Jest测试并行执行失败
尝试用TypeORM(搭配PostgreSQL)编写Jest测试,期望测试能并行执行,且每个测试启动事务、执行后回滚事务。当前测试配置及遇到的问题如下:
当前测试代码
let queryRunner: QueryRunner let em: EntityManager; beforeAll(async () => { const testAppDataSource = await getTestAppDataSource() queryRunner = testAppDataSource.createQueryRunner() queryRunner.connect() }) beforeEach(async () => { await queryRunner.startTransaction() em = queryRunner.manager }) afterEach(async () => { await queryRunner.rollbackTransaction() }) afterAll(async () => { await queryRunner.release() }) test("testname", async() => { // perform operations with em })
getTestAppDataSource 实现
const testAppDataSource = new DataSource(dataSourceOptions) export const getTestAppDataSource = async () => { if (!testAppDataSource.isInitialized) { await testAppDataSource.initialize() } return testAppDataSource }
报错信息
使用--runInBand串行执行时一切正常,但并行执行时出现以下错误:
QueryFailedError: duplicate key value violates unique constraint "pg_type_typname_nsp_index"
TypeError: Cannot read properties of undefined (reading 'rollbackTransaction')
通常仅有1个测试能成功,怀疑测试间存在资源干扰,猜测dataSource.createQueryRunner()给所有测试返回了同一个QueryRunner实例。
补充说明:曾尝试将const testAppDataSource = await getTestAppDataSource()替换为以下代码,但问题依然存在:
const testAppDataSource = new DataSource(testDataSourceOptions) await testAppDataSource.initialize()
问题核心在于多个并行测试共享了同一个QueryRunner和数据源实例,导致事务操作冲突、资源竞争。Jest并行执行时,每个测试文件会在独立进程中运行,但模块级单例变量会被缓存,同时全局的queryRunner变量会被多个测试用例同时修改,引发未定义错误。
1. 为每个测试文件创建独立的数据源和QueryRunner
不要使用单例数据源,而是在每个测试文件中初始化独立的数据源实例,并且为每个测试用例创建专属的QueryRunner:
let queryRunner: QueryRunner; let em: EntityManager; let dataSource: DataSource; beforeAll(async () => { // 每个测试文件初始化独立的数据源 dataSource = new DataSource({ ...dataSourceOptions, // 可选:调整连接池大小,避免并行测试耗尽连接 poolSize: 10, }); await dataSource.initialize(); }); beforeEach(async () => { // 每个测试用例创建新的QueryRunner queryRunner = dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); em = queryRunner.manager; }); afterEach(async () => { // 每个测试用例回滚事务并释放QueryRunner if (queryRunner) { await queryRunner.rollbackTransaction(); await queryRunner.release(); } }); afterAll(async () => { // 测试文件结束后销毁数据源 await dataSource.destroy(); }); test("testname", async () => { // 使用当前测试用例的em执行操作 });
2. 移除模块级单例数据源
删除原有的getTestAppDataSource单例实现,确保每个测试文件都能创建独立的数据源实例,避免Jest模块缓存导致的共享问题。
3. 禁用Jest模块缓存(可选)
如果依然存在模块缓存问题,可以在测试文件顶部添加:
jest.resetModules();
或者在Jest配置文件中设置resetModules: true,确保每个测试文件加载时模块都被重新初始化。
内容的提问来源于stack exchange,提问作者sev

