NgModel 绑定在内部不适用于<form>茉莉花测试



在我的 Angular 5.2.0 项目中,我有以下结构:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private _title = 'initial value';
public get title(): string {
return this._title;
}
public set title(v: string) {
this._title = v;
}
}

app.component.spec.ts

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
imports: [FormsModule]
}).compileComponents();
}));
it('should bind an input to a property', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
fixture.detectChanges();
// Update the title input
const inputElement = fixture.debugElement.query(By.css('input[name="title"]')).nativeElement;
inputElement.value = 'new value';
inputElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(app.title).toEqual('new value');
});
}));
});

对于以下测试通过:

app.component.html

<input name="title" type="text" [(ngModel)]="title">

但是如果我将输入放入表单标签中,测试将失败:

app.component.html

<form>
<input name="title" type="text" [(ngModel)]="title">
</form>

Chrome 67.0.3396 (Windows 7 0.0.0( AppComponent 應將輸入到屬性 FAILED 预期"初始值"等于"新值"。

知道为什么会发生这种情况以及如何解决此问题吗?

第一个解决方案(使用 fakeAsync + tick(:

it('should bind an input to a property', fakeAsync(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
fixture.detectChanges();
tick();
const inputElement = fixture.debugElement.query(By.css('input[name="title"]')).nativeElement;
inputElement.value = 'new value';
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
tick();
expect(app.title).toEqual('new value');
}));

第二种解决方案(使用同步和一些代码重构(:

describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let app: AppComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({...}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
app = fixture.debugElement.componentInstance;
fixture.detectChanges(); // this call is required
}));
it('should bind an input to a property', async(() => {
const inputElement = fixture.debugElement.query(By.css('input[name="title"]')).nativeElement;
inputElement.value = 'new value';
inputElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
expect(app.title).toEqual('new value');
});
}));
...

知道为什么会这样吗?

根据官方的Angular文档:

模板驱动的表单将其表单控件的创建委托给指令。为了避免在检查错误后更改,这些指令需要多个周期来构建整个控制树。这意味着您必须等到下一个更改检测周期发生,然后才能从组件类中操作任何控件。

例如,如果使用 @ViewChild(NgForm( 查询注入窗体控件,并在 ngAfterViewInit 生命周期挂钩中检查它,则会发现它没有子项。必须先使用 setTimeout(( 触发更改检测周期,然后才能从控件中提取值、测试其有效性或将其设置为新值。

附言在Angular的GitHub存储库中也有一个类似的问题(dispatchEvent不会触发ngModel更改#13550(,您也可以查看一下。

最新更新