测试不识别模板驱动形式中的默认 属性 `form.invalid`

Test not recognising default property `form.invalid` in a template driven form

我想测试一个模板驱动的表单。最初我希望禁用表单的按钮,因为我想确保用户输入名称。这是通过名称输入字段中的 'required' 属性 完成的。

所以我创建了以下模板:

<form #form="ngForm">
  <!-- note the 'required' property on the input field -->
  <input
    type="text"
    name="name"
    id="name"
    #name="ngModel"
    required
    ngModel
  />
  <button type="submit" [disabled]="form.invalid">Submit</button>
  {{ form.invalid | json }}
</form>

这在浏览器中呈现得很好:

现在我想测试一下这个行为。这就是它变得时髦的地方。测试如下:

import { async, ComponentFixture, TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

describe("AppComponent", () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  let el: DebugElement;
  let button: HTMLButtonElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      imports: [CommonModule, FormsModule]
    }).compileComponents();
  }));
  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    el = fixture.debugElement;
    fixture.detectChanges();
    button = el.query(By.css("button")).nativeElement;
  });
  describe("submit button", () => {
    it("should be disabled by default", () => {
      expect(button.nativeNode.disabled).toBeTrue();
    });
  });
});

我的测试失败了,它无法识别 form.invalid 属性。它将其设置为 'false':

所以我最好的猜测是 Angular 应用程序的实际 'ng build' 或 'ng serve' 做了一些魔术,将 ngForm 设置为具有无效的 属性 如果输入值之一不符合要求。

但是我该如何在测试中执行此操作?

我希望测试尽可能接近现实世界的例子。因此必须手动将 form.invalid 设置为 true 有点违背了测试的目的。

这是上述代码的 Stackblitz 示例:https://stackblitz.com/edit/angular-ivy-pfnkfc

所以好像有一些异步任务需要完成(我们等待fixture.whenStable然后我们需要调用fixture.detectChanges()。这也是我几乎使用反应形式的原因之一总是因为它更容易测试。您可以仅通过 TypeScript 代码测试它们,而根本不必查看 HTML。

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  let el: DebugElement;
  let button: HTMLButtonElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      imports: [FormsModule]
    }).compileComponents();
  }));
  beforeEach(async () => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    el = fixture.debugElement;
    fixture.detectChanges();
  });

  describe('submit button', () => {
    it('should be disabled by default', async () => {
      await fixture.whenStable(); // wait for all asynchronous tasks to complete
      fixture.detectChanges(); // call detectChanges again
      button = fixture.debugElement.query(By.css('button')).nativeElement;
      expect(button.disabled).toBe(true);
    });
  });
});

注意事项:

1.) 每次调用 fixture.detectChanges 时,都需要对 fixture.debugElement.query... 的新引用,因为每次调用 fixture.detectChanges 时视图都会发生变化。如果我们像在 beforeEach 中那样继续使用 el,您将对 HTML 的外观有一个旧的参考。

2.) 您不需要在 Angular 单元测试中导入 CommonModule