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

如何为NestJS中TypeORM createQueryBuilder的findAll方法编写Jest测试用例

问题:TypeORM createQueryBuilder Jest测试报错 "Cannot read property 'leftJoinAndSelect' of undefined"

我正在为服务中的findAll方法编写Jest测试用例,该方法使用TypeORM的createQueryBuilder实现带分页的数据查询,查阅多篇文章后仍无法定位问题,急需帮助。

服务代码

async findAll(getPaginationParamDto: PaginationParamDto): Promise<GetCcAdminResponseDto> {
    try {
      const result = this.ccAdminRepo
        .createQueryBuilder()
        .leftJoinAndSelect('CcAdmin.userId', 'userId')
        .where({ isArchived: false });
      const total = await result.getCount();
      const totalPages =
        total % getPaginationParamDto.limit === 0
          ? total / getPaginationParamDto.limit
          : Math.floor(total / getPaginationParamDto.limit) + 1;

      const data = await result.offset(getPaginationParamDto.offset).limit(getPaginationParamDto.limit).getMany();
      // Create custom response according to swagger
      const customData = data.map((data) => {
        if (data.userId) {
          const userId = data.userId.userId;
          return {
            ...data,
            userId: userId,
          };
        }
      });
      return {
        total,
        totalPages,
        data: customData,
      };
    } catch (error) {
      console.log(error);
      throw error;
    }
  }

测试用例代码

describe('CcAdminService', () => {
  let ccAdminService: CcAdminService;
  let sandbox: sinon.SinonSandbox;
  beforeAll(async () => {
    sandbox = sinon.createSandbox();
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CcAdminService,
        {
          provide: getRepositoryToken(CcAdmin),
          useValue: sinon.createStubInstance(Repository),
        },
      ],
    }).compile();
    ccAdminService = module.get<CcAdminService>(CcAdminService);
  });

  it('should call get all method with expected params', async () => {
    const createQueryBuilder: any = {
      leftJoinAndSelect: () => createQueryBuilder,
      where: () => createQueryBuilder,
    };

    const getPaginationParamDto = new PaginationParamDto();
    const findAllCcAdminSpy = jest.spyOn(ccAdminService, 'findAll');
    await ccAdminService.findAll(getPaginationParamDto);
    expect(findAllCcAdminSpy).toHaveBeenCalledWith(createQueryBuilder);
  });
  afterAll(async () => {
    sandbox.restore();
  });
});

运行错误

Cannot read property 'leftJoinAndSelect' of undefined


解决方案

问题根源

你通过sinon.createStubInstance(Repository)创建的Repository stub,没有模拟createQueryBuilder方法的返回值,导致调用this.ccAdminRepo.createQueryBuilder()时返回undefined,后续链式调用leftJoinAndSelect自然抛出错误。另外,测试断言逻辑完全错误——findAll的入参是PaginationParamDto,不是你定义的createQueryBuilder对象。

修正后的测试代码

describe('CcAdminService', () => {
  let ccAdminService: CcAdminService;
  let ccAdminRepo: sinon.SinonStubbedInstance<Repository<CcAdmin>>;
  let sandbox: sinon.SinonSandbox;

  beforeAll(async () => {
    sandbox = sinon.createSandbox();
    // 创建Repository stub并手动模拟createQueryBuilder
    ccAdminRepo = sandbox.createStubInstance(Repository);
    
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CcAdminService,
        {
          provide: getRepositoryToken(CcAdmin),
          useValue: ccAdminRepo,
        },
      ],
    }).compile();

    ccAdminService = module.get<CcAdminService>(CcAdminService);
  });

  it('should return paginated data correctly', async () => {
    // 模拟查询返回的原始数据
    const mockRawData = [
      { userId: { userId: 'user1' }, isArchived: false },
      { userId: { userId: 'user2' }, isArchived: false },
    ];
    // 模拟转换后的自定义数据
    const mockCustomData = mockRawData.map(item => ({ ...item, userId: item.userId.userId }));
    const mockTotal = 2;
    const mockLimit = 10;
    const mockOffset = 0;
    const mockTotalPages = 1;

    // 构建链式调用的QueryBuilder stub
    const queryBuilderStub = {
      leftJoinAndSelect: sandbox.stub().returnsThis(),
      where: sandbox.stub().returnsThis(),
      getCount: sandbox.stub().resolves(mockTotal),
      offset: sandbox.stub().returnsThis(),
      limit: sandbox.stub().returnsThis(),
      getMany: sandbox.stub().resolves(mockRawData),
    };

    // 让Repository的createQueryBuilder返回上面的stub
    ccAdminRepo.createQueryBuilder.returns(queryBuilderStub);

    const paginationDto = new PaginationParamDto();
    paginationDto.limit = mockLimit;
    paginationDto.offset = mockOffset;

    const result = await ccAdminService.findAll(paginationDto);

    // 验证QueryBuilder的方法是否被正确调用
    expect(queryBuilderStub.leftJoinAndSelect).toHaveBeenCalledWith('CcAdmin.userId', 'userId');
    expect(queryBuilderStub.where).toHaveBeenCalledWith({ isArchived: false });
    expect(queryBuilderStub.offset).toHaveBeenCalledWith(mockOffset);
    expect(queryBuilderStub.limit).toHaveBeenCalledWith(mockLimit);
    expect(queryBuilderStub.getCount).toHaveBeenCalled();
    expect(queryBuilderStub.getMany).toHaveBeenCalled();

    // 验证返回结果是否符合预期
    expect(result.total).toBe(mockTotal);
    expect(result.totalPages).toBe(mockTotalPages);
    expect(result.data).toEqual(mockCustomData);
  });

  afterAll(async () => {
    sandbox.restore();
  });
});

关键修正点

  1. 手动模拟Repository的createQueryBuilder方法,让它返回预定义的queryBuilderStub,确保链式调用的每个方法都返回自身(returnsThis())。
  2. 补充模拟getCount、getMany等异步方法的返回值,确保服务能正常执行完所有逻辑。
  3. 修正断言逻辑:不再错误断言findAll的入参,而是验证QueryBuilder的各个方法是否被正确调用,以及返回结果是否符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:45:30