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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:41:34