如何为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(); }); });
关键修正点
- 手动模拟
Repository的createQueryBuilder方法,让它返回预定义的queryBuilderStub,确保链式调用的每个方法都返回自身(returnsThis())。 - 补充模拟
getCount、getMany等异步方法的返回值,确保服务能正常执行完所有逻辑。 - 修正断言逻辑:不再错误断言
findAll的入参,而是验证QueryBuilder的各个方法是否被正确调用,以及返回结果是否符合预期。
内容的提问来源于stack exchange,提问作者Zenixo
相关产品推荐
相关产品推荐

