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

使用ng-mocks测试ngModel的单元测试失败问题及解决方案

Ng-Mocks测试ngModel时的问题与解决

问题重现

在使用ng-mocks测试六位数输入组件的退格行为时,以下单元测试中标记行断言失败:

it('输入框有内容时按退格,不应移动焦点且清除当前输入值', fakeAsync(() => {
    const fixture = MockRender(SixDigitInputComponent);
    const inputs = ngMocks.findAll('input');
    const focusSpy = spyOn(inputs[0].nativeElement, 'focus');
    
    ngMocks.change(inputs[0], 3);
    ngMocks.change(inputs[1], 2);

    expect(inputs[0].nativeElement.value).toBe('3');
    expect(inputs[1].nativeElement.value).toBe('2');

    ngMocks.trigger(inputs[1], 'keydown.backspace');
    fixture.detectChanges();
    tick();
    expect(inputs[0].nativeElement.value).toBe('3'); // 断言失败:期望''等于'3'
    expect(inputs[1].nativeElement.value).toBe('');

    expect(focusSpy).not.toHaveBeenCalled();
}));

其余断言均正常,但将ngMocks.change替换为以下手动设置值并触发input事件的代码后,所有断言通过:

inputs[0].nativeElement.value = 3;
inputs[0].nativeElement.dispatchEvent(new Event('input'));
inputs[1].nativeElement.value = 2;
inputs[1].nativeElement.dispatchEvent(new Event('input'));

组件代码

模板(some.component.html)

<div class="code-input-container">
    <input
        #inputField
        (paste)="pasteValue($event)"
        (keydown.backspace)="clearFieldAndBacktrack(0)"
        (ngModelChange)="writeAndMoveToNext($event, 0)"
        [ngModel]="displayNumbers[0]"
        type="text"
        maxlength="1"
        placeholder="0"
    />
    <input
        #inputField
        (paste)="pasteValue($event)"
        (keydown.backspace)="clearFieldAndBacktrack(1)"
        (ngModelChange)="writeAndMoveToNext($event, 1)"
        [ngModel]="displayNumbers[1]"
        maxlength="1"
        type="text"
        placeholder="0"
    />
    <input
        #inputField
        (paste)="pasteValue($event)"
        (keydown.backspace)="clearFieldAndBacktrack(2)"
        (ngModelChange)="writeAndMoveToNext($event, 2)"
        [ngModel]="displayNumbers[2]"
        type="text"
        maxlength="1"
        placeholder="0"
    />
</div>

逻辑(some.component.ts)

public writeAndMoveToNext(digit: string, i: number): void {
    if (!digit || !this.regex.test(digit)) {
        return;
    }
    this.displayNumbers[i] = Number(digit);
    this.focusOn(i + 1);
    this.onTouch();
    this.onChangeFn(this.displayNumbers.join(''));
}

public clearFieldAndBacktrack(i: number): void {
    if (i === -1) {
        return;
    }
    // 第二次按退格时,清除当前框并聚焦上一个输入框
    if (this.displayNumbers[i] === null) {
        this.clearFieldAndBacktrack(i - 1);
        return;
    }
    this.focusOn(i);
    this.displayNumbers[i] = null;
    this.onTouch();
    this.onChangeFn(this.displayNumbers.join(''));
}

疑问

为何ngMocks.change未触发DOM中的ngModel变更,而手动设置值并触发事件却可以?

最终解决方案

调整测试代码,添加fixture.detectChanges()同步模型与视图,并通过fixture.whenStable()等待异步操作完成:

it('输入框为空时按退格,应聚焦上一个输入框并清除其值', fakeAsync(() => {
    const fixture = MockRender(SixDigitInputComponent);
    const inputs = ngMocks.findAll('input');
    const focusSpy = spyOn(inputs[0].nativeElement, 'focus');

    ngMocks.change(inputs[0], '3');
    ngMocks.change(inputs[1], '2');
    fixture.detectChanges(); // 同步模型到视图

    expect(inputs[0].nativeElement.value).toBe('3');
    expect(inputs[1].nativeElement.value).toBe('2');

    ngMocks.trigger(inputs[1], 'keydown.backspace');
    fixture.detectChanges();
    fixture.whenStable().then(() => {
        expect(inputs[0].nativeElement.value).toBe('3');
        expect(inputs[1].nativeElement.value).toBe('');
        expect(focusSpy).not.toHaveBeenCalled();
    });
}));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:16:36