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

NestJS Jest单元测试:自定义Mock实现未被控制器调用

问题:NestJS Jest测试中使用VpcServiceMock替代真实服务报错TypeError: this.vpcService.list is not a function

我已实现VpcServiceMock类,希望在NestJS的Jest单元测试中用其替代真实VpcService,但运行测试时报错:TypeError: this.vpcService.list is not a function。


相关代码

控制器代码

export class VpcController {
  constructor(private readonly vpcService: VpcService) {}
  @Get()
  list() {
    return this.vpcService.list();
  }
}

测试代码

class VpcServiceMockFactory {
  private list() {
    return jest.fn().mockResolvedValue([TEMPLATE_VPC]);
  }

  getMock() {
    const repoService: RepositoryService = new RepositoryService();
    const vpsServiceMock: VpcServiceMock = new VpcServiceMock(repoService);
    vpsServiceMock.create(TEMPLATE_VPC.name);

    // 此写法生效
    // return {
    //   list: this.list(),
    // }

    // 此写法报错
    return {
      list: vpsServiceMock.list(),
      get: vpsServiceMock.get(TEMPLATE_VPC.id),
      // create: vpsServiceMock.create('new-vpc'),
      // update: vpsServiceMock.update(TEMPLATE_VPC.id, 'updated-name'),
      // delete: vpsServiceMock.delete(TEMPLATE_VPC.id),
    }
  }
}
describe('VpcControllerTest', () => {
  let controller: VpcController;
  let spyService: VpcService;

  beforeEach(async () => {
    // Mock Services
    const MockVpcServiceProvider = {
      provide: VpcService,
      useFactory: () => new VpcServiceMockFactory().getMock()
    }

    // 待测试类实例化
    const module: TestingModule = await Test.createTestingModule({
      controllers: [VpcController],
      providers: [VpcService, MockVpcServiceProvider],
    }).compile()

    // 获取实例
    controller = module.get<VpcController>(VpcController);
    spyService = module.get<VpcService>(VpcService);
  });

  it('Get collection of VPCs', async () => {
    // 执行方法
    const result = await controller.list();

    // 断言
    expect(spyService.list).toHaveBeenCalled();
    expect(result.length).toBe(1);
    expect(result[0].name).toBe('zopa');
  });
});

VpcServiceMock类代码

export class VpcServiceMock {
  constructor(private repository: RepositoryService) {}
  list() {
    return this.repository.list<VpcModel>(VPC);
  }
}

RepositoryService类的list方法

async list<T>(type: VPC): Promise<T[]> {
  return <T[]>this.aSimpleJsonObject[type];
}

问题原因

  • 函数调用时机错误:在getMock方法中,你直接执行了vpsServiceMock.list()并将返回值(一个Promise对象)赋值给list属性。当控制器调用this.vpcService.list()时,实际是试图调用一个Promise而非函数,因此触发is not a function错误。
  • 生效写法的差异:之前生效的list: this.list()中,this.list()返回的是Jest mock函数(jest.fn()),属于函数类型,所以控制器调用时能正常执行。

解决方案

方式一:直接返回Mock类实例并Spy方法(推荐)

修改VpcServiceMockFactory.getMock,直接返回VpcServiceMock实例,同时用Jest spy方法以便后续断言:

getMock() {
  const repoService: RepositoryService = new RepositoryService();
  const vpsServiceMock: VpcServiceMock = new VpcServiceMock(repoService);
  vpsServiceMock.create(TEMPLATE_VPC.name);
  
  // Spy mock类的方法,保留原有逻辑同时支持调用断言
  jest.spyOn(vpsServiceMock, 'list');
  // 如果需要自定义get方法的返回值,可以添加:
  // jest.spyOn(vpsServiceMock, 'get').mockResolvedValue(TEMPLATE_VPC);
  
  return vpsServiceMock; // 直接返回mock实例
}

同时调整测试模块的providers配置,移除重复的VpcService(否则Nest会优先注入真实服务):

const module: TestingModule = await Test.createTestingModule({
  controllers: [VpcController],
  providers: [
    {
      provide: VpcService,
      useFactory: () => new VpcServiceMockFactory().getMock()
    }
  ],
}).compile();

方式二:手动包裹方法为函数

如果坚持手动构造返回对象,需要将方法包裹成箭头函数,避免直接执行:

return {
  list: () => vpsServiceMock.list(), // 用箭头函数包裹,返回原方法执行结果
  get: (id: string) => vpsServiceMock.get(id),
  // 其他方法同理
};

注意:这种方式无法直接用expect(spyService.list).toHaveBeenCalled()断言,需要额外处理,因此优先推荐方式一。


内容的提问来源于stack exchange,提问作者Pradip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 15:45:52