为什么在Jasmine Angular单元测试中不触发反应形式控件的更改事件?

时间:2018-08-15 21:54:15

标签: angular typescript unit-testing jasmine angular-reactive-forms

我有一个传递给变更事件的变更功能,用于反应性表单控件,该控件评估状态并检查控件上是否有任何错误如果是这样,则将boolean标志设置为true / false。然后,此布尔标志用于确定是否显示具有错误消息的<div>元素。这在浏览器中可以正常工作,但是在运行单元测试时,永远不会将“ dirty”设置为true。这是我的代码:

HTML

<form [formGroup]="myForm" novalidate>
    <input id="age" formControlName="age" (change)="onChange()" />
    <div id="ageError" *ngIf="ageIsError()">
        <label>Age has errored</label>
    </div>
</form>

组件

constructor(private fb: FormBuilder) {}

ngOnInit() {
    this.myForm = this.fb.group({
        age: [null, [Validators.min(18)]]
    });
}

onChange() {
    if (this.ageIsError())
        // do something
}

ageIsError() {
    return this.myForm.controls.age.hasError('min') &&
           this.myForm.controls.age.dirty;
}

单元测试

it('should show error message when age is less than 18', fakeAsync(() => {
    let age = component.myForm.controls.age;
    age.setValue('10', { emitEvent: true });
    fixture.detectChanges();

    fixture.whenStable().then(() => {
        let ageError = debugElement.query(By.css('#ageError')).nativeElement;
        expect(component.ageIsError()).toBe(true);
        expect(ageError.innerText).toContain('Age has errored');
    });
}));

同样,实际的实现在浏览器中有效,但是单元测试失败。有谁知道要在茉莉花中发出事件以将控件设置为脏状态,还是有更好的方法来实现?谢谢!

2 个答案:

答案 0 :(得分:1)

在您的示例中,age.setValue(...)实际上为输入设置了正确的值,但没有将ageError附加到DOM-因为没有真实/模拟事件将控件标记为脏。因此,在这种情况下,方法ageIsError总是返回false

作为一种解决方法,我只是使用document.createEvent('Event')模拟了输入事件,并且看起来工作正常:

  it('should show error message when age is less than 18', async(() => {
    const customEvent: Event = document.createEvent('Event');
    customEvent.initEvent('input', false, false);
    const ageInput = fixture.debugElement.query(By.css('input#age')).nativeElement;
    ageInput.value = 10;
    ageInput.dispatchEvent(customEvent);

    fixture.detectChanges();

    fixture.whenStable().then(() => {
      let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
      expect(component.ageIsError()).toBe(true);
      expect(ageError.innerText).toContain('Age has errored');
    });
  }));

我还找到了解决方案的解决方案-只需在age.markAsDirty()之前致电detectChanges

  it('should show error message when age is less than 18', async(() => {
    let age = component.myForm.controls.age;
    age.setValue('10'); // { emitEvent: true } is by default
    age.markAsDirty(); // add this line

    fixture.detectChanges();

    fixture.whenStable().then(() => {
        let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
        expect(component.ageIsError()).toBe(true);
        expect(ageError.innerText).toContain('Age has errored');
    });
  }));

我还创建了一个stackblitz example,也请检查一下。希望这些解决方案对您有所帮助:)

答案 1 :(得分:0)

表单控件绑定会自动更新。

以下代码位于测试块中。它只是获取一个输入字段并在其上发送一个更新值。

然后我渲染出控制器状态并可以确认绑定自动工作。

  const usernameInput: HTMLInputElement = fixture.nativeElement.querySelector(
            '#username'
        );
        usernameInput.value = 'testValueNotEmail';
        usernameInput.dispatchEvent(new Event('input'));

        // FormGroup and Controll Bindings and Validations were automatically updated after the event.
        console.log(component.loginForm.getRawValue());
        console.log(component.loginForm.get('username')?.errors);
        console.log(component.loginForm.get('username')?.touched);
        console.log(component.loginForm.get('username')?.dirty);

        // In order for the fixture render element changes to take effect,
        // I had to detect the changes. The form error fields were then rendered. 
        fixture.detectChanges();
        const errorField = fixture.nativeElement.querySelector(
            '#usernameSmall'
        );
        console.log(errorField);