NgModel绑定在Jasmine测试的<form>内部不起作用

时间:2018-08-31 08:56:26

标签: angular jasmine angular5 jasmine2.0

在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         预期“初始值”等于“新值”。

有什么想法为什么会发生以及如何解决?

1 个答案:

答案 0 :(得分:1)

第一个解决方案(使用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');
    });
  }));
  ...
  

知道为什么会发生吗?

根据official Angular docs

  

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

     

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

p.s。此外,Angular's GitHub repo中也存在类似的问题(dispatchEvent不会触发ngModel更改#13550),您也可以查看一下。