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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 14:45:43