Angular 14中Jest测试私有方法cacheValueChanges失败排查
问题描述
我在Angular组件中定义了如下私有方法:
private cacheValueChanges() { const cacheControl = this.form.get(CATALOG_INPUT.MAX_CACHE) as AbstractControl; this.subscriptions.add( cacheControl.valueChanges.subscribe((value) => { if (value && this.isValidationAdded) { this.resetValidationFieldCacheForm(cacheControl); } }) ) }
该私有方法在NgOnInit中被调用,用于检测名为cache的表单输入框的值变化。当输入框有值且组件的私有属性isValidationAdded为true时,会调用另一个私有方法:
private resetValidationFieldCacheForm(cacheControl: AbstractControl) { this.isValidationAdded = false; this.createActivityService.addValidateMaxCache(this.apiValidation, cacheControl, this.isValidationAdded); cacheControl.updateValueAndValidity({emitEvent: false}); }
我需要用Jest测试私有方法cacheValueChanges,以及满足条件时resetValidationFieldCacheForm的调用逻辑。以下是我编写的Jest测试用例,但运行报错:
describe('CreateActivityComponent', () => { let createActivityService: CreateActivityService; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ CreateActivityComponent ], imports: [ TranslateModule.forRoot(), HttpClientTestingModule, ReactiveFormsModule, FormsModule, ], }) .compileComponents(); fixture = TestBed.createComponent(CreateActivityComponent); component = fixture.componentInstance; createActivityService = TestBed.inject(CreateActivityService); }); describe('cacheValueChanges', () => { it('should reset validation field cache form when cache control value changes and validation is added', () => { const spyCreateActivity = jest.spyOn(createActivityService, 'addValidateMaxCache'); component.ngOnInit(); const cacheMaxControl = component.form.get(CATALOG_INPUT.MAX_CACHE) as AbstractControl; cacheMaxControl.setValue('200'); expect(spyCreateActivity).toHaveBeenCalledWith({}, cacheMaxControl!, false); }); }); });
报错信息:
CreateActivityComponent › cacheValueChanges › should reset validation field cache form when cache control value changes and validation is added expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: {}, {"_composedAsyncValidatorFn": null, "_composedValidatorFn": [Function anonymous], "_hasOwnPendingAsyncValidator": false, "_onChange": [], "_onCollectionChange": [Function anonymous], "_onDisabledChange": [], "_parent": {"_composedAsyncValidatorFn": null, "_composedValidatorFn": null, "_hasOwnPendingAsyncValidator": false, "_onCollectionChange": [Function anonymous], "_onDisabledChange": [Array], "_parent": null, "_pendingDirty": false, "_pendingTouched": false, "_rawAsyncValidators": null, "_rawValidators": null, "controls": [Object], "errors": null, "pristine": true, "status": "INVALID", "statusChanges": [EventEmitter_], "touched": false, "value": [Object], "valueChanges": [EventEmitter_]}, "_pendingChange": false, "_pendingDirty": false, "_pendingTouched": false, "_pendingValue": "200", "_rawAsyncValidators": null, "_rawValidators": [[Function required], [Function anonymous], [Function anonymous], [Function anonymous], [Function anonymous]], "defaultValue": null, "errors": null, "pristine": true, "status": "VALID", "statusChanges": {"__isAsync": false, "closed": false, "currentObservers": [Array], "hasError": false, "isStopped": false, "observers": [Array], "thrownError": null}, "touched": false, "value": "200", "valueChanges": {"__isAsync": false, "closed": false, "currentObservers": [Array], "hasError": false, "isStopped": false, "observers": [Array], "thrownError": null}}, false Number of calls: 0 241 | cacheMaxControl.setValue('200'); 242 | > 243 | expect(spyCreateActivity).toHaveBeenCalledWith({}, cacheMaxControl!, false); | ^ 244 | }); 245 | });
看起来是我在执行expect(spyCreateActivity).toHaveBeenCalledWith({}, cacheMaxControl!, false);时参数传递有误,请问问题出在哪里?
问题分析与修复
你的测试用例失败有两个核心原因:
1. 未满足触发条件:isValidationAdded未设置为true
组件中cacheValueChanges的订阅逻辑只有在value && this.isValidationAdded为真时才会执行后续操作,而测试代码里没有初始化isValidationAdded的值(默认是false),导致订阅回调根本没触发,addValidateMaxCache一次都没被调用。
2. 第一个参数匹配错误:apiValidation不等于空对象{}
在resetValidationFieldCacheForm中,调用服务方法时传入的是this.apiValidation,但测试里用了空对象{}来匹配,除非组件的apiValidation默认就是空对象,否则这个匹配会失败。
另外,表单值变更后需要触发变更检测,确保订阅回调被执行。
修正后的测试用例
describe('CreateActivityComponent', () => { let createActivityService: CreateActivityService; let fixture: ComponentFixture<CreateActivityComponent>; let component: CreateActivityComponent; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ CreateActivityComponent ], imports: [ TranslateModule.forRoot(), HttpClientTestingModule, ReactiveFormsModule, FormsModule, ], }) .compileComponents(); fixture = TestBed.createComponent(CreateActivityComponent); component = fixture.componentInstance; createActivityService = TestBed.inject(CreateActivityService); }); describe('cacheValueChanges', () => { it('should reset validation field cache form when cache control value changes and validation is added', () => { // 1. 初始化必要的组件属性,满足触发条件 component.isValidationAdded = true; // 模拟apiValidation的值(如果实际有值,替换成对应内容) component.apiValidation = {}; const spyCreateActivity = jest.spyOn(createActivityService, 'addValidateMaxCache'); // 2. 调用ngOnInit启动订阅 component.ngOnInit(); // 触发变更检测,确保订阅生效 fixture.detectChanges(); const cacheMaxControl = component.form.get(CATALOG_INPUT.MAX_CACHE) as AbstractControl; cacheMaxControl.setValue('200'); // 再次触发变更检测,确保值变更的回调执行 fixture.detectChanges(); // 3. 用组件实际的apiValidation和control来匹配 expect(spyCreateActivity).toHaveBeenCalledWith( component.apiValidation, cacheMaxControl, false ); }); }); });
额外优化建议
- 如果
apiValidation是通过服务初始化的,建议在测试前mock对应的服务返回值,而不是直接赋值。 - 测试后记得清理订阅,避免内存泄漏:可以在
afterEach里调用component.subscriptions.unsubscribe()(如果subscriptions是Subscription类型)。
内容的提问来源于stack exchange,提问作者Eladerezador
相关产品推荐
相关产品推荐

