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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:52:34