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

Jest单元测试问题:调用父方法后如何验证子方法被调用?

Jest单元测试问题:调用父方法后无法检测子方法调用

问题概述

调用DefaultDatabaseService的deleteDatabase方法后,测试无法检测到deleteParkingReservation等子方法的调用,报错提示expect(jest.fn()).toHaveBeenCalled()期望调用次数≥1,但实际为0。

相关代码

DefaultDatabaseService.ts

async deleteDatabase(): Promise<string> {
    if (process.env.NODE_ENV === 'dev') {
      try {
        await this.deleteParkingReservation();
        await this.deleteRoomReservation();
        await this.deleteParkingSpace();
        await this.deleteRoomSpace();
        await this.deleteUser();
        return `All database was deleted.`;
      } catch (error) {
        throw new NotFoundException('Could not delete database!', error);
      }
    }
    return 'You must use DEV environment for that!';
  }

测试代码spec.ts

const modelStub: any = {
  exec: jest.fn().mockReturnThis(),
  deleteDatabase: jest.fn(),
  deleteMany: jest.fn().mockReturnThis(),
  deleteParkingReservation: jest.fn(),
  deleteRoomReservation: jest.fn().mockReturnThis(),
  deleteParkingSpace: jest.fn().mockReturnThis(),
  deleteRoomSpace: jest.fn().mockReturnThis(),
  deleteUser: jest.fn().mockReturnThis()
};

describe('DefaultDatabaseService', () => {
  let service: DefaultDatabaseService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        DefaultDatabaseService,
        { provide: getModelToken('RoomReservationSpace'), useValue: modelStub },
        { provide: getModelToken('ParkingReservationSpace'), useValue: modelStub },
        { provide: getModelToken('RoomReservation'), useValue: modelStub },
        { provide: getModelToken('ParkingReservation'), useValue: modelStub },
        { provide: getModelToken('User'), useValue: modelStub }
      ]
    }).compile();

    service = module.get<DefaultDatabaseService>(DefaultDatabaseService);
  });

  it('should call deleteDatabase method', async () => {
    process.env.NODE_ENV = 'dev';
    await service.deleteDatabase();
    if (process.env.NODE_ENV === 'dev') {
      await expect(modelStub.deleteParkingReservation).toHaveBeenCalled();
    }
  }); 

错误原因

你误解了方法的归属:deleteParkingReservation是DefaultDatabaseService类的实例方法,而非你注入的Model对象的方法。当前测试中断言的是modelStub.deleteParkingReservation被调用,但实际执行的是service实例自身的deleteParkingReservation方法,两者完全无关,所以断言失败。

修复方案

方案1:Mock并断言服务实例的子方法

通过jest.spyOnMock服务实例的子方法,直接断言这些方法是否被调用:

describe('DefaultDatabaseService', () => {
  let service: DefaultDatabaseService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        DefaultDatabaseService,
        // 保持原有的Model注入配置
        { provide: getModelToken('RoomReservationSpace'), useValue: modelStub },
        { provide: getModelToken('ParkingReservationSpace'), useValue: modelStub },
        { provide: getModelToken('RoomReservation'), useValue: modelStub },
        { provide: getModelToken('ParkingReservation'), useValue: modelStub },
        { provide: getModelToken('User'), useValue: modelStub }
      ]
    }).compile();

    service = module.get<DefaultDatabaseService>(DefaultDatabaseService);
    
    // Mock服务实例的子方法,避免执行真实逻辑
    jest.spyOn(service, 'deleteParkingReservation').mockResolvedValue();
    jest.spyOn(service, 'deleteRoomReservation').mockResolvedValue();
    jest.spyOn(service, 'deleteParkingSpace').mockResolvedValue();
    jest.spyOn(service, 'deleteRoomSpace').mockResolvedValue();
    jest.spyOn(service, 'deleteUser').mockResolvedValue();
  });

  it('should call all sub methods when deleteDatabase runs in dev env', async () => {
    process.env.NODE_ENV = 'dev';
    await service.deleteDatabase();

    // 断言服务实例的子方法被调用
    expect(service.deleteParkingReservation).toHaveBeenCalled();
    expect(service.deleteRoomReservation).toHaveBeenCalled();
    expect(service.deleteParkingSpace).toHaveBeenCalled();
    expect(service.deleteRoomSpace).toHaveBeenCalled();
    expect(service.deleteUser).toHaveBeenCalled();
  }); 
});

方案2:断言Model的实际方法被调用(更推荐)

如果deleteParkingReservation等子方法的内部逻辑是调用注入的Model的deleteMany或其他方法,那么应该直接断言Model的方法是否被调用(这更贴近业务逻辑的验证):

假设deleteParkingReservation的实现如下:

async deleteParkingReservation() {
  await this.parkingReservationModel.deleteMany({});
}

修改测试断言:

it('should trigger Model delete methods when deleteDatabase runs in dev env', async () => {
  process.env.NODE_ENV = 'dev';
  await service.deleteDatabase();

  // 断言Model的deleteMany方法被调用
  expect(modelStub.deleteMany).toHaveBeenCalled();
});

额外优化:环境变量的隔离

为了避免测试之间的环境变量污染,建议在测试前后重置环境变量:

beforeEach(() => {
  process.env.NODE_ENV = 'dev';
});

afterEach(() => {
  delete process.env.NODE_ENV;
});

或者使用Jest的replaceProperty更安全地修改环境变量:

it('...', async () => {
  jest.replaceProperty(process.env, 'NODE_ENV', 'dev');
  await service.deleteDatabase();
  // 断言逻辑
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:31:03