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

Angular限制小数位数指令测试:排查测试用例缺失环节

问题:Angular小数限制指令测试用例未按预期工作

我正在测试一个用于控制小数分隔符及允许小数位数的Angular指令InstrumentDecimalsDirective,该指令监听ngControl的valueChanges,当输入小数位数超出限制时将值回退为旧值。我已创建测试组件InstrumentDecimalTestComponent及对应测试用例,但测试未按预期工作,请指出测试用例中的缺失部分。

指令代码

@Directive({
    selector: '[restrictedDecimals]'
})
export class InstrumentDecimalsDirective implements OnInit {
    @Input() decimals = null;
    @Input() separator = ',';
    valueSubscription: Subscription;

    constructor(@Self() @Optional() public ngControl: NgControl) {}

    ngOnInit(): void {
        this.ngControl.valueChanges.pipe(distinctUntilChanged(), pairwise()).subscribe(([oldValue, newValue]) => {
            this.runCheck(oldValue, newValue);
        });
    }
...
}

测试组件模板

<input type="text" restrictedDecimals decimals="2" />

测试用例代码

describe('InstrumentDecimalsDirective', () => {
   let fixture: ComponentFixture<any>;
   beforeEach(async () => {
       const NG_CONTROL_PROVIDER = {
           provide: NgControl,
           useClass: class extends NgControl {
               control = new FormControl();
               viewToModelUpdate() {}
           }
       };

       const testModuleMetadata: TestModuleMetadata = {
           imports: [ReactiveFormsModule, FormsModule],
           declarations: [InstrumentDecimalTestComponent, InstrumentDecimalsDirective]
       };
       fixture = TestBed.configureTestingModule(testModuleMetadata).overrideDirective(InstrumentDecimalsDirective, {
           add: { providers: [NG_CONTROL_PROVIDER] },
         }).createComponent(InstrumentDecimalTestComponent);
       fixture.detectChanges();
   });


   it('should reject input with more decimals than allowed', fakeAsync(() => {
       const input = fixture.debugElement.queryAll(By.directive(InstrumentDecimalsDirective))[0].nativeElement;
       const inputEvent = new Event('input');
       input.value = '1,12';
       input.dispatchEvent(inputEvent);
       input.value = '1,123';
       input.dispatchEvent(inputEvent);

       tick(500);
       fixture.detectChanges();
       expect(input.value).toBe('1,12');
   }));

});

测试用例的缺失点及修复方案
  • 未同步表单控件值与输入框视图值
    直接修改input.value不会同步到ngControl的FormControl中,而指令监听的是ngControl.valueChanges,因此只有控件值更新时才会触发校验逻辑。需要通过指令实例直接操作FormControl来设置值:

    it('should reject input with more decimals than allowed', () => {
        const inputEl = fixture.debugElement.query(By.directive(InstrumentDecimalsDirective));
        const directive = inputEl.injector.get(InstrumentDecimalsDirective);
        const input = inputEl.nativeElement;
    
        // 先设置合法值到控件
        directive.ngControl.control.setValue('1,12');
        fixture.detectChanges();
        // 再设置非法值
        directive.ngControl.control.setValue('1,123');
        fixture.detectChanges();
    
        expect(directive.ngControl.control.value).toBe('1,12');
        expect(input.value).toBe('1,12');
    });
    
  • 忽略了pairwise操作符的触发条件
    pairwise需要至少两个值才会发射数据,若控件初始值为null/undefined,第一次设置'1,12'不会触发订阅回调,第二次设置'1,123'才会触发。建议在测试前给控件设置初始空值:

    beforeEach(() => {
        // ... 现有初始化代码
        const directive = fixture.debugElement.query(By.directive(InstrumentDecimalsDirective)).injector.get(InstrumentDecimalsDirective);
        directive.ngControl.control.setValue('');
        fixture.detectChanges();
    });
    
  • 不必要的fakeAsync与tick
    指令的valueChanges是同步流(无异步操作符),tick(500)属于冗余操作,直接移除即可,同步执行校验逻辑。

  • 未验证视图与模型的双向同步
    指令的runCheck若仅回退了控件值,可能未同步到输入框视图。测试时需同时断言FormControl值和输入框value,确保两者一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:46:05