测试实现ControlValueAccessor的组件ngModel与formControl变更是否与视图同步
问题根源
你的测试失败是组件逻辑的问题,核心和ChangeDetectionStrategy.OnPush变更检测策略的触发规则有关:
OnPush策略下,只有三种情况会触发组件的变更检测更新视图:
- 组件的输入属性值发生变化
- 组件内部触发了DOM事件(比如点击、输入等)
- 手动调用
ChangeDetectorRef.markForCheck()标记视图为需要检查
而ControlValueAccessor的writeValue方法是Angular表单体系主动调用的API,不属于前两种自动触发变更检测的场景,你在writeValue里更新了input属性后,没有通知变更检测器,所以视图不会同步更新。
修复步骤
1. 调整组件逻辑,注入变更检测器并标记视图
给组件注入ChangeDetectorRef,在writeValue赋值完成后标记视图需要检查:
import { ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'some-selector', styleUrls: ['some.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SomeComponent), // 注意示例代码中此处类名和实际组件类名不一致,要修改为正确值 multi: true, }, ], changeDetection: ChangeDetectionStrategy.OnPush, template: ` <!-- 这里可以简化写法,不需要拿元素ref取value,$event就是输入值 --> <input [ngModel]="input" (ngModelChange)="onChange($event); input = $event"> `, }) export class SomeComponent implements ControlValueAccessor { input = ''; onTouched: () => void = () => ({}); onChange: (value: string) => void = () => ({}); // 注入变更检测器 constructor(private cdr: ChangeDetectorRef) {} writeValue(input: string): void { this.input = input; // 新增:标记视图需要变更检测 this.cdr.markForCheck(); } registerOnChange(fn: (v: string) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } }
2. 调整测试用例,补全初始渲染触发
第二个直接测试组件的用例失败,是因为漏了初始渲染的变更检测调用,调整后如下:
it('should synchronise ngModel\'s change with view', () => { // 先触发一次初始渲染,完成组件初始化流程 fixture.detectChanges(); component.input = 'someValue'; fixture.detectChanges(); const inputRef = fixture.debugElement.query(By.css('input')); expect(inputRef.nativeElement.value).toBe('someValue'); });
修改完成后两个测试用例都可以正常通过。
内容的提问来源于stack exchange,提问作者Dawid Zbiński
相关产品推荐
相关产品推荐

