使用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
相关产品推荐
相关产品推荐

