如何在Jest单元测试中模拟TypeORM EntityManager事务做行为断言
问题
我想要为一个使用TypeORM EntityManager事务的方法编写单元测试,目标方法定义如下:
class MyRepository { private readonly repository: Repository<MyEntity>; myFunction() { this.repository.manager.transaction(async (trx: EntityManager) => { await trx.delete(MyEntity, { id: "123" }); }); } }
我希望模拟事务,断言delete方法在事务内被调用,预期的测试代码大致如下:
const repository: jest.Mocked<Repository<MyEntity>>; // ... describe("when calling myFunction", () => { it("should call the delete method on the transaction", async () => { await myRepository.myFunction(); expect(deleteMock).toHaveBeenCalled(); }); });
但我不确定如何正确模拟事务和delete方法。我尝试过Mock Repository:
const deleteMock = jest.fn(); function mockRepository<E extends Object>(): jest.Mocked<Repository<E>> { return { // ...其他模拟属性 manager: { transaction: jest.fn().mockReturnValue({ delete: deleteMock, }), } as jest.Mocked<Partial<EntityManager>> as jest.Mocked<EntityManager>, } as jest.Mocked<Partial<Repository<E>>> as jest.Mocked<Repository<E>>; }
现在只能断言manager.transaction方法被调用,但无法断言deleteMock被调用,测试失败。请问如何用Jest实现预期的测试?
解决方案
你的问题出在对transaction方法的模拟逻辑上——TypeORM的transaction方法是接收一个回调函数,把事务管理器trx作为参数传给这个回调执行,而非直接返回带delete方法的对象。之前的模拟只返回了对象,但没有触发传入的回调,导致回调里的trx.delete从未执行。
正确的模拟需要让transaction方法执行传入的回调,并把预定义好的模拟事务管理器(包含deleteMock)传给它。同时还要注意原方法的异步返回逻辑,确保测试能等待内部操作完成。
完整测试代码示例
import { Repository, EntityManager } from 'typeorm'; // 补全MyRepository的构造函数,方便注入mock实例 class MyRepository { private readonly repository: Repository<MyEntity>; constructor(repository: Repository<MyEntity>) { this.repository = repository; } // 修正:返回transaction的Promise,确保测试能await到内部异步操作完成 async myFunction() { return this.repository.manager.transaction(async (trx: EntityManager) => { await trx.delete(MyEntity, { id: "123" }); }); } } // 测试逻辑 describe('MyRepository', () => { let myRepository: MyRepository; let deleteMock: jest.Mock; let mockEntityManager: jest.Mocked<EntityManager>; let mockRepository: jest.Mocked<Repository<MyEntity>>; beforeEach(() => { // 1. 创建delete方法的mock函数 deleteMock = jest.fn().mockResolvedValue({ affected: 1 }); // 2. 构建模拟的EntityManager,挂载deleteMock mockEntityManager = { delete: deleteMock, // 其他EntityManager方法可按需mock } as jest.Mocked<EntityManager>; // 3. 构建模拟的Repository,让transaction方法执行传入的回调 mockRepository = { manager: { transaction: jest.fn().mockImplementation(async (callback) => { // 执行回调并传入模拟的事务管理器 return callback(mockEntityManager); }), } as jest.Mocked<EntityManager>, // 其他Repository方法可按需mock } as jest.Mocked<Repository<MyEntity>>; // 4. 注入mock实例,创建MyRepository对象 myRepository = new MyRepository(mockRepository); }); describe('when calling myFunction', () => { it('should call delete method on the transaction manager', async () => { await myRepository.myFunction(); // 断言transaction方法被调用 expect(mockRepository.manager.transaction).toHaveBeenCalled(); // 断言delete方法被调用,且参数符合预期 expect(deleteMock).toHaveBeenCalledWith(MyEntity, { id: "123" }); }); }); });
关键说明
- 修正
myFunction的返回值:原方法没有返回transaction的Promise,测试时await无法等待内部异步操作完成,会导致断言提前执行,此时deleteMock还未被调用,测试必然失败。 transaction的模拟逻辑:用mockImplementation替代mockReturnValue,在模拟函数内部主动执行传入的回调,并把我们的mockEntityManager作为参数传入,这样回调里的trx.delete就会触发deleteMock。- 类型兼容处理:通过
as jest.Mocked<EntityManager>的类型断言,避免TypeScript的类型检查报错。
内容的提问来源于stack exchange,提问作者Sergio Yepes
相关产品推荐
相关产品推荐

