如何在 Angular 测试中使用 waitForAsync 和 fakeAsync

介绍

角2+提供asyncfakeAsync用于测试异步代码工具。这应该会让你的 Angular 单元和集成测试更容易编写。

在本文中,您将了解waitForAsyncfakeAsync使用示例测试。

先决条件

要完成本教程,您需要:

本教程已通过 Node v16.4.0、npmv7.19.0 和@angular/corev12.1.1 验证。

设置项目

首先,使用@angular/cli创建一个新项目:

  • ng new angular-async-fakeasync-example

然后,导航到新创建的项目目录:

  • cd angular-async-fakeasync-example

这将创建一个新的角度项目app.component.htmlapp.compontent.tsapp.component.spec.ts文件。

测试 waitForAsync

waitForAsync实用程序告诉 Angular 在拦截承诺的专用测试区中运行代码。我们在 Angular 中使用单元测试介绍中简要介绍了异步实用程序compileComponents

whenStable实用程序允许我们等到所有承诺都得到解决才能运行我们的期望。

首先打开app.component.ts并使用Promiseresolvetitle

src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title!: string;

  setTitle() {
    new Promise(resolve => {
      resolve('Async Title!');
    }).then((val: any) => {
      this.title = val;
    });
  }
}

然后打开app.component.html,并用其替换h1button

src/app/app.component.html
<h1>
  {{ title }}
</h1>

<button (click)="setTitle()" class="set-title">
  Set Title
</button>

单击按钮时,title将使用承诺设置属性。

以下是我们如何使用waitForAsyncand测试此功能的方法whenStable

src/app/app.component.spec.ts
import { TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  });

  it('should display title', waitForAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('.set-title'))
      .triggerEventHandler('click', null);

    fixture.whenStable().then(() => {
      fixture.detectChanges();
      const value = fixture.debugElement
        .query(By.css('h1'))
        .nativeElement
        .innerText;
      expect(value).toEqual('Async Title!');
    });
  }));
});

注意:在真实的应用程序中,您将有实际等待有用的东西的承诺,例如从请求到后端 API 的响应。

此时,您可以运行您的测试:

  • ng test

这将产生一个成功的'should display title'测试结果。

测试 fakeAsync

问题async在于我们仍然必须在测试中引入真正的等待,这会使我们的测试非常缓慢。fakeAsync来拯救并帮助以同步方式测试异步代码。

为了演示fakeAsync,让我们从一个简单的例子开始。假设我们的组件模板有一个按钮,它增加一个值,如下所示:

src/app/app.component.html
<h1>
  {{ incrementDecrement.value }}
</h1>

<button (click)="increment()" class="increment">
  Increment
</button>

它调用increment组件类中的一个方法,如下所示:

src/app/app.component.ts
import { Component } from '@angular/core';
import { IncrementDecrementService } from './increment-decrement.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(public incrementDecrement: IncrementDecrementService) { }

  increment() {
    this.incrementDecrement.increment();
  }
}

而这个方法本身调用了incrementDecrement服务中的一个方法

  • ng generate service increment-decrement

它有一个increment使用 a 异步方法setTimeout

src/app/increment-decrement.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IncrementDecrementService {
  value = 0;
  message!: string;

  increment() {
    setTimeout(() => {
      if (this.value < 15) {
        this.value += 1;
        this.message = '';
      } else {
        this.message = 'Maximum reached!';
      }
    }, 5000); // wait 5 seconds to increment the value
  }
}

显然,在现实世界的应用程序中,这种异步性可以通过多种不同的方式引入。

现在让我们使用fakeAsynctick实用程序运行集成测试并确保该值在模板中递增:

src/app/app.component.spec.ts
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ]
    }).compileComponents();
  });

  it('should increment in template after 5 seconds', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    tick(2000);

    fixture.detectChanges();
    const value1 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value1).toEqual('0'); // value should still be 0 after 2 seconds

    tick(3000);

    fixture.detectChanges();
    const value2 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value2).toEqual('1'); // 3 seconds later, our value should now be 1
  }));
});

请注意如何在tick内使用实用程序fakeAsync来模拟时间的流逝。传入的参数tick是要传递的毫秒数,这些在测试中是累积的。

注意: Tick也可以不带参数使用,在这种情况下,它会等待所有微任务完成(例如,当 promise 被解析时)。

此时,您可以运行您的测试:

  • ng test

这将产生一个成功的'should increment in template after 5 seconds'测试结果。

像这样指定经过的时间很快就会变得很麻烦,并且当您不知道应该经过多少时间时可能会成为一个问题。

flushAngular 4.2 中引入了一个名为的新实用程序,有助于解决该问题。它模拟时间流逝,直到宏任务队列为空。宏任务包括像setTimoutssetIntervalsrequestAnimationFrame

因此,使用flush,我们可以编写这样的测试,例如:

src/app/app.component.spec.ts
import { TestBed, fakeAsync, flush } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ]
    }).compileComponents();
  });

  it('should increment in template', fakeAsync(() => {
    const fixture = TestBed.createComponent(AppComponent);

    fixture.debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    flush();
    fixture.detectChanges();

    const value = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
    expect(value).toEqual('1');
  }));
});

此时,您可以运行您的测试:

  • ng test

这将产生一个成功的'should increment in template'测试结果。

结论

在本文中,您了解waitForAsyncfakeAsync使用了示例测试。

您还可以参考官方文档以获取深入的 Angular 测试指南。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁