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

NestJS:继承CrudController的控制器单元测试报错排查

解决NestJS控制器单元测试中Mock服务未正确实例化的问题

问题概述

编写promotions.controller.ts的单元测试时,调用performDelete方法抛出错误:

TypeError: Cannot read property 'findOne' of undefined

findOne是PromotionsService继承自CrudService的方法,尝试通过Jest模拟服务但未生效。

错误原因

  1. Mock服务未提供必要方法:mockPromotionsService是空对象,没有定义控制器中用到的findOne、performDelete等方法,导致this.model调用对应方法时触发undefined错误。
  2. 提前执行业务方法:在beforeEach中直接调用了promotionsController.performDelete,此时测试环境尚未完全准备好,应该将该调用移到具体测试用例内。
  3. Mock函数未正确配置:未使用jest.fn()创建可跟踪的mock函数,无法验证方法调用情况,也无法返回模拟数据。

修复步骤

1. 完善Mock服务的方法定义

给mockPromotionsService和mockOrganizationsService添加控制器用到的所有方法,并用jest.fn()模拟,同时返回符合业务逻辑的测试数据:

const mockOrganizationsService = {
  sbQueue: jest.fn().mockResolvedValue(true)
};

const mockPromotionsService = {
  findOne: jest.fn().mockResolvedValue({ id: '1234', organization: 'test-org' }),
  performDelete: jest.fn().mockResolvedValue({ id: '1234' })
};

2. 调整测试执行时机

将beforeEach中调用performDelete的代码移除,放到具体的测试用例中执行,避免提前触发业务逻辑。

3. 验证Mock函数的调用情况

在测试用例中,通过toHaveBeenCalled()或toHaveBeenCalledWith()验证服务方法是否被正确调用。

完整修复后的测试代码

import { PromotionsService } from "../promotions.service";
import { PromotionsController } from "../promotions.controller";
import { Test, TestingModule } from "@nestjs/testing";
import { OrganizationsService } from "../../organizations/organizations.service";

describe('PromotionsController', () => {
  let promotionsController: PromotionsController;
  let promotionsService: PromotionsService;
  let organizationsService: OrganizationsService;

  const mockOrganizationsService = {
    sbQueue: jest.fn().mockResolvedValue(true)
  };

  const mockPromotionsService = {
    findOne: jest.fn().mockResolvedValue({ id: '1234', organization: 'test-org' }),
    performDelete: jest.fn().mockResolvedValue({ id: '1234' })
  };

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [PromotionsController],
      providers: [PromotionsService, OrganizationsService]
    })
      .overrideProvider(PromotionsService).useValue(mockPromotionsService)
      .overrideProvider(OrganizationsService).useValue(mockOrganizationsService)
      .compile();

    promotionsService = moduleRef.get<PromotionsService>(PromotionsService);
    organizationsService = moduleRef.get<OrganizationsService>(OrganizationsService);
    promotionsController = moduleRef.get<PromotionsController>(PromotionsController);
  });

  describe('基础验证', () => {
    it('控制器应被正确实例化', () => {
      expect(promotionsController).toBeDefined();
    });

    it('服务应被正确实例化', () => {
      expect(promotionsService).toBeDefined();
      expect(organizationsService).toBeDefined();
    });
  });

  describe('performDelete方法', () => {
    it('应正确调用服务方法并返回结果', async () => {
      const result = await promotionsController.performDelete({ id: '1234' });
      
      expect(result).toEqual({ id: '1234' });
      expect(promotionsService.findOne).toHaveBeenCalledWith({ id: '1234' });
      expect(organizationsService.sbQueue).toHaveBeenCalledWith({
        type: "promotion",
        ids: ['1234'],
        delete: true,
        organization: 'test-org',
      });
      expect(promotionsService.performDelete).toHaveBeenCalledWith({ id: '1234' });
    });
  });
});

关键说明

  • 所有被控制器调用的服务方法都需要在Mock对象中定义,否则会触发undefined错误。
  • 使用jest.fn().mockResolvedValue()模拟异步方法的返回值,确保异步逻辑能正常执行。
  • 将业务方法的调用放到测试用例中,保证测试的独立性和可跟踪性。

内容的提问来源于stack exchange,提问作者Zhi Hang Tan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 01:45:31