NgModel绑定在内部不起作用 for Jasmine test

问题描述 投票:1回答:1

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

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

angular jasmine angular5 jasmine2.0
1个回答
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()触发更改检测周期,然后才能从控件中提取值,测试其有效性或将其设置为新值。

附:在Angular's GitHub repo中也存在类似的问题(dispatchEvent不会触发ngModel更改#13550),您也可以查看它。

© www.soinside.com 2019 - 2024. All rights reserved.