Angular迁移Jest测试报toHaveBeenCalledOnceWith类型不存在
问题描述
项目新增3个JSON文件作为动态配置,相关配置会在应用初始化阶段加载。将原有Jasmine-Karma测试代码迁移至Jest后,运行测试时出现如下类型报错:
Property 'toHaveBeenCalledOnceWith' does not exist on type 'JestMatchers<Mock<any, any>>'.
关联项目代码
app.module.ts 配置
export function configLoader(injector: Injector) : () => Promise<any> { return () => injector.get(ConfigurationService).loadConfiguration(); } export function configProdLoader(injector: Injector) : () => Promise<any> { return () => injector.get(ConfigurationService).loadProdConfig(); } export function configEnvironmentLoader(injector: Injector) : () => Promise<any> { return () => injector.get(ConfigurationService).loadEnvironmentConfig(); }
App模块的providers配置如下:
{provide: APP_INITIALIZER, useFactory: configLoader, deps: [Injector], multi: true}, {provide: APP_INITIALIZER, useFactory: configProdLoader, deps: [Injector], multi: true}, {provide: APP_INITIALIZER, useFactory: configEnvironmentLoader, deps: [Injector], multi: true},
test.spec.ts 测试代码
describe("ConfigurationService", () => { const returnValue = {}; let httpMock: {get: jest.Mock}; let service: ConfigurationService; beforeEach(() => { httpMock = { get: jest.fn(() => of(returnValue)), }; service = new ConfigurationService(<any>httpMock); }); test('Should call the endpoint and retrieve the config', (done) => { service.loadConfiguration().then(() => { expect(httpMock.get) .toHaveBeenCalledOnceWith(service['configPath']); expect(service['configData']).toBe(returnValue); done(); }); }); test('Should call the endpoint and retrieve the configProd', (done) => { service.loadProdConfig().then(() => { expect(httpMock.get) .toHaveBeenCalledOnceWith(service['configProdPath']); expect(service['configProdData']).toBe(returnValue); done(); }); }); test('Should call the endpoint and retrieve the configEnvironment', (done) => { service.loadEnvironmentConfig().then(() => { expect(httpMock.get) .toHaveBeenCalledOnceWith(service['configEnvironmentPath']); expect(service['configEnvironmentData']).toBe(returnValue); done(); }); }); });
service.ts 服务实现代码
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ConfigurationService { private configData: any | undefined; private configProdData: any | undefined; private configEnvironmentData: any | undefined; private readonly configPath: string = '../../assets/config/aws-config.json'; private readonly configProdPath: string = '../../assets/config/prod-config.json'; private readonly configEnvironmentPath: string = '../../assets/config/environment-config.json'; constructor(private httpClient: HttpClient) { } async loadConfiguration(): Promise<any> { try { const response = await this.httpClient.get(`${this.configPath}`) .toPromise().then(res => this.configData = res); return this.configData; } catch (err) { return Promise.reject(err); } } get config(): any | undefined { return this.configData; } async loadProdConfig(): Promise<any> { try { const response = await this.httpClient.get(`${this.configProdPath}`) .toPromise().then(res => this.configProdData = res); return this.configProdData; } catch (err) { return Promise.reject(err); } } get configProd(): any | undefined { return this.configProdData; } async loadEnvironmentConfig(): Promise<any> { try { const response = await this.httpClient.get(`${this.configEnvironmentPath}`) .toPromise().then(res => this.configEnvironmentData = res); return this.configEnvironmentData; } catch (err) { return Promise.reject(err); } } get configEnvironmentProd(): any | undefined { return this.configEnvironmentData; } }
问题根因
- 核心报错原因:
toHaveBeenCalledOnceWith是Jasmine测试框架专属的内置匹配器,Jest原生没有提供这个API,从Karma+Jasmine技术栈迁移到Jest后直接调用该方法,就会触发类型不存在的报错。 - 测试代码隐性缺陷:三个测试用例复用同一个
httpMock.get的mock实例,没有在用例执行前重置mock状态,不同用例的调用记录会互相污染,会导致断言结果不符合预期。
修复方案
方案1:替换为Jest原生断言(无额外依赖,推荐)
用两个Jest原生断言组合实现和toHaveBeenCalledOnceWith完全一致的校验逻辑:
- 用
toHaveBeenCalledTimes(1)校验mock方法仅被调用1次 - 用
toHaveBeenCalledWith(目标参数)校验mock方法入参符合预期
同时在beforeEach钩子中增加mock重置逻辑,避免用例间状态污染。
修改后的测试代码核心片段:
beforeEach(() => { httpMock = { get: jest.fn(() => of(returnValue)), }; // 每个用例执行前清空所有mock的调用记录、缓存状态 jest.clearAllMocks(); service = new ConfigurationService(<any>httpMock); }); test('Should call the endpoint and retrieve the config', (done) => { service.loadConfiguration().then(() => { // 替换原有的toHaveBeenCalledOnceWith断言 expect(httpMock.get).toHaveBeenCalledTimes(1); expect(httpMock.get).toHaveBeenCalledWith(service['configPath']); expect(service['configData']).toBe(returnValue); done(); }); }); // 其余两个测试用例按照相同规则替换断言即可
方案2:引入Jest兼容插件(保留原有断言写法)
如果不想修改已有的断言逻辑,可以安装jest-jasmine2运行器,或在Jest setup文件中手动扩展匹配器,让Jest兼容toHaveBeenCalledOnceWithAPI。该方案会增加项目依赖,维护成本更高,不作为优先选择。
内容的提问来源于stack exchange,提问作者ftslaptop pc
相关产品推荐
相关产品推荐

