NestJS:继承CrudController的控制器单元测试报错排查
解决NestJS控制器单元测试中Mock服务未正确实例化的问题
问题概述
编写promotions.controller.ts的单元测试时,调用performDelete方法抛出错误:
TypeError: Cannot read property 'findOne' of undefined
findOne是PromotionsService继承自CrudService的方法,尝试通过Jest模拟服务但未生效。
错误原因
- Mock服务未提供必要方法:
mockPromotionsService是空对象,没有定义控制器中用到的findOne、performDelete等方法,导致this.model调用对应方法时触发undefined错误。 - 提前执行业务方法:在
beforeEach中直接调用了promotionsController.performDelete,此时测试环境尚未完全准备好,应该将该调用移到具体测试用例内。 - 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
相关产品推荐
相关产品推荐

