Jest单元测试中无法mock nestjs-typeorm-paginate的paginate函数
问题背景
我需要对服务的findAll方法做单元测试mock,目前有两种思路:要么mock myEntityRepository的findAndCount仓库方法,要么mock nestjs-typeorm-paginate npm包的paginate函数。其中findAll方法负责从数据表拉取记录列表,且使用了nestjs-typeorm-paginate模块实现分页能力。
尝试方案1:mock myEntityRepository
执行时报错,错误栈如下:
TypeError: queryBuilder.limit is not a function at ../node_modules/nestjs-typeorm-paginate/dist/paginate.js:119:28 at ../node_modules/nestjs-typeorm-paginate/dist/paginate.js:8:71 at Object.<anonymous>.__awaiter (../node_modules/nestjs-typeorm-paginate/dist/paginate.js:4:12) at paginateQueryBuilder (../node_modules/nestjs-typeorm-paginate/dist/paginate.js:115:12) at Object.<anonymous> (../node_modules/nestjs-typeorm-paginate/dist/paginate.js:22:15) at ../node_modules/nestjs-typeorm-paginate/dist/paginate.js:8:71
my.service.ts代码:
import { IPaginationOptions, paginate, Pagination } from 'nestjs-typeorm-paginate' export class MyService { constructor(@InjectRepository(MyEntity) private myEntityRepository: Repository<MyEntity>) { } async findAll(options: IPaginationOptions): Promise<Pagination<MyEntity>> { try { return await paginate<MyEntity>(this.myEntityRepository, options) } catch (error) { throw error } } }
my.service.spec.ts代码:
describe('MyService Basic GET findAll test cases', () => { let service: MyService let repositoryMock: MockType<Repository<MyEntity>> beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [MyService, { provide: getRepositoryToken(MyEntity), useFactory: repositoryMockFactory } ], }).compile() service = module.get<MyService>(MyService) repositoryMock = module.get(getRepositoryToken(MyEntity)) const itemList = [{ id: 1, my_field: 'a1', }, { id: 2, my_field: 'a2', }, ] }) it('should findAll() the MyEntity', async () => { expect((await service.findAll(options)).items.length).toBe(itemsList.length) }) }) const repositoryMockFactory: () => MockType<Repository<MyEntity>> = jest.fn(() => ({ find: jest.fn(entity => entity), findAndCount: jest.fn(entity => entity), }))
方案1报错原因
nestjs-typeorm-paginate的paginate方法接收Repository实例时,会优先尝试调用createQueryBuilder方法构建查询,你mock的Repository只实现了find和findAndCount,缺少createQueryBuilder方法的实现,所以内部生成的queryBuilder对象没有limit等方法,才会抛出该错误。
尝试方案2:mock paginate方法
修改后仍然抛出错误:
TypeError: Cannot redefine property: paginate at Function.defineProperty (<anonymous>)
修改后的my.service.spec.ts代码:
import * as nestjsTypeormPaginate from 'nestjs-typeorm-paginate' // imported at top .... .... it('should findAll() the MyEntity', async () => { const queryDto: QueryMyEntityDto = { customerId: 1 } const options: IPaginationOptions = { page: 1, limit: 10 } let paginationMock = jest.spyOn(nestjsTypeormPaginate, 'paginate') paginationMock.mockImplementation((dto, options) => Promise.resolve({ items: itemList.slice(0, 2), meta: { itemCount: 2, totalItems: 2, totalPages: 1, currentPage: 1, } })) repositoryMock.find.mockReturnValue(itemList) expect((await service.findAll(options)).items.length).toBe(itemsList.length) }) ...
方案2报错原因
nestjs-typeorm-paginate导出的paginate属性默认是不可配置的,jest.spyOn无法直接修改不可配置的属性,所以抛出重定义错误。
解决方案
两种可行实现任选即可:
方案A:补全Repository的mock方法(适配原方案1)
paginate方法如果检测到Repository存在findAndCount方法,就会优先使用该方法而不调用createQueryBuilder,你只需要把mock的findAndCount返回值改成符合TypeORM规范的格式即可:
// 修改repositoryMockFactory的实现 const repositoryMockFactory: () => MockType<Repository<MyEntity>> = jest.fn(() => ({ findAndCount: jest.fn().mockResolvedValue([ // 第一个元素是查询到的实体列表 [{ id: 1, my_field: 'a1' }, { id: 2, my_field: 'a2' }], // 第二个元素是总记录数 2 ]), }))
修改后不需要额外mock其他方法,直接执行测试即可正常运行。
方案B:在jest配置层面mock整个paginate模块(适配原方案2)
不要用spyOn的方式修改单个导出属性,直接在测试文件顶部用jest.mock mock整个模块:
// 在测试文件import语句的最上方添加 jest.mock('nestjs-typeorm-paginate', () => ({ ...jest.requireActual('nestjs-typeorm-paginate'), paginate: jest.fn(), })); import * as nestjsTypeormPaginate from 'nestjs-typeorm-paginate'; // 测试用例里直接指定mock返回值即可 it('should findAll() the MyEntity', async () => { const options: IPaginationOptions = { page: 1, limit: 10 }; const itemList = [{ id: 1, my_field: 'a1' }, { id: 2, my_field: 'a2' }]; (nestjsTypeormPaginate.paginate as jest.Mock).mockResolvedValue({ items: itemList, meta: { itemCount: 2, totalItems: 2, totalPages: 1, currentPage: 1, } }); expect((await service.findAll(options)).items.length).toBe(itemList.length); })
内容的提问来源于stack exchange,提问作者Vibhor Verma

