如何用Jest和NestJS正确模拟注入仓库?解析器测试遇方法未定义错误
NestJS测试中PostService模拟失效导致
Cannot read properties of undefined (reading 'findById')的解决方法 问题场景
在NestJS+GraphQL+Jest的项目中,测试PostsResolver的findPosts方法时触发错误:
● PostsResolver › findPosts › should return data TypeError: Cannot read properties of undefined (reading 'findById') 22 | { id }: FindPostsInput, 23 | ) { > 24 | return await this.postService.findById( | ^ 25 | id, 26 | ); at PostsResolver.findPosts (src/resolver/query/post.ts:24:41) at Object.<anonymous> (test/unit/resolver/post.spec.ts:59:42)
相关代码片段:
PostService实现(src/service/post.ts)
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { AbstractPostRepository } from '@abstractRepository/AbstractPostRepository'; import { PostRepository } from '@repository/PostRepository'; @Injectable() export class PostService { constructor( @InjectRepository(PostRepository) private postRepo: AbstractPostRepository, ) {} async findById(id: string) { const posts = await this.postRepo.findById(id); // ...其他逻辑 return posts; } }
PostsResolver实现(src/resolver/query/post.ts)
import { Args, Query, Resolver } from '@nestjs/graphql'; import { Authorized } from '@lib/auth/authorized'; import { PermissionScope } from '@lib/auth/permissionScope'; import { PostService } from '@service/postService'; @Resolver() export class PostsResolver { constructor(private postService: PostService) {} @PermissionScope() @Authorized([ PermissionEnum.READ_MANAGEMENT, ]) @Query() async findPosts(@Args() { id }) { return await this.postService.findById(id); } }
测试代码(test/unit/resolver/post.spec.ts)
import { Test, TestingModule } from '@nestjs/testing'; import { PERMISSION_MANAGER } from '@lib/auth/permissionManager.module'; import { PostsResolver } from '@resolver/query/post'; import { PostService } from '@service/postService'; describe('PostsResolver', () => { let resolver: PostsResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ PostsResolver, { provide: PERMISSION_MANAGER, useValue: jest.fn(), }, { provide: PostService, useFactory: () => ({ findById: jest.fn(() => [ { id: '11111111-1111-1111-1111-111111111111', name: 'name1', }, ]), }), }, ], }).compile(); resolver = module.get<PostsResolver>(PostsResolver); }); describe('findPosts', () => { it('should return data', async () => { const posts = await resolver.findPosts({ id: '11111111-1111-1111-1111-111111111111', }); const expected = [ { id: '11111111-1111-1111-1111-111111111111', name: 'name1', }, ]; expect(posts).toEqual(expected); }); }); });
解决方法
方法1:手动实例化Resolver,避免模块注入干扰
直接跳过Test.createTestingModule,手动创建Resolver实例并传入模拟的PostService,这种方式更直接,能规避权限装饰器或其他依赖配置带来的问题:
import { PostsResolver } from '@resolver/query/post'; import { PostService } from '@service/postService'; describe('PostsResolver', () => { let resolver: PostsResolver; let postServiceMock: Partial<PostService>; beforeEach(() => { // 创建PostService的模拟实现 postServiceMock = { findById: jest.fn(() => [ { id: '11111111-1111-1111-1111-111111111111', name: 'name1', }, ]), }; // 手动实例化Resolver,传入模拟对象 resolver = new PostsResolver(postServiceMock as PostService); }); describe('findPosts', () => { it('should return data', async () => { const posts = await resolver.findPosts({ id: '11111111-1111-1111-1111-111111111111', }); const expected = [ { id: '11111111-1111-1111-1111-111111111111', name: 'name1', }, ]; expect(posts).toEqual(expected); // 额外验证findById方法是否被正确调用 expect(postServiceMock.findById).toHaveBeenCalledWith('11111111-1111-1111-1111-111111111111'); }); }); });
方法2:修复模块注入配置问题
如果坚持使用Test.createTestingModule,需要确保:
- 权限装饰器依赖的
PERMISSION_MANAGER模拟足够完善,避免干扰Resolver实例化 - 确保PostService的模拟token与实际注入的token完全匹配
可以给PERMISSION_MANAGER的模拟添加装饰器所需的必要方法:
{ provide: PERMISSION_MANAGER, useValue: { checkPermissions: jest.fn().mockResolvedValue(true), // 根据装饰器实际需求添加其他方法 }, }
同时检查Resolver中的findPosts方法参数是否正确接收id,避免语法错误导致的额外问题。
内容的提问来源于stack exchange,提问作者Alpin Cleopatra
相关产品推荐
相关产品推荐

