如何开始Angular的单元测试

来自菜鸟教程
跳转至:导航、​搜索

介绍

如果您的项目是使用 Angular CLI 创建的,那么一切准备就绪,您可以开始使用 Jasmine 作为测试框架并使用 Karma 作为测试运行器来编写测试.

Angular 还提供了诸如 TestBedasync 之类的实用程序,以简化异步代码、组件、指令或服务的测试。

在本文中,您将了解如何使用 Jasmine 和 Karma 在 Angular 中编写和运行单元测试。

先决条件

要完成本教程,您需要:

  • Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
  • 熟悉 设置 Angular 项目

本教程已使用 Node v16.2.0、npm v7.15.1 和 @angular/core v12.0.4 进行了验证。

第 1 步 — 设置项目

您的测试文件通常放在他们测试的文件旁边,但如果您愿意,它们也可以放在自己单独的目录中。

这些规范文件使用 *.spec.ts 的命名约定。

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

ng new angular-unit-test-example

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

cd angular-unit-test-example

除了 app.component 之外,还有一个 app.component.spec.ts 文件。 打开此文件并检查其内容:

src/app/app.component.spec.ts

import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

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

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'angular-unit-test-example'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual('angular-unit-test-example');
  });

  it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('.content span').textContent).toContain('angular-unit-test-example app is running!');
  });
});

了解茉莉花

首先,关于茉莉花的一些重要知识:

  • describe 块定义了一个测试套件,每个 it 块用于单独的测试。
  • beforeEach 在每次测试之前运行,用于测试的 setup 部分。
  • afterEach 在每次测试后运行,用于测试的 teardown 部分。
  • 您还可以使用 beforeAllafterAll,它们在所有测试之前或之后运行一次。
  • 您使用 expect 并使用 toBeDefinedtoBeTruthytoContaintoEqual、[ X121X]、toBeNull、... 例如:expect(myValue).toBeGreaterThan(3);
  • 您可以使用 not 进行否定断言:expect(myValue).not.toBeGreaterThan(3);
  • 您还可以定义自定义匹配器。

TestBed 是可用于 Angular 特定测试的主要实用程序。 您将在测试套件的 beforeEach 块中使用 TestBed.configureTestingModule 并为其提供一个与 declarations、[ 的常规 NgModule 具有相似值的对象X142X] 和 imports。 然后你可以调用 compileComponents 来告诉 Angular 编译声明的组件。

您可以使用 TestBed.createComponent 创建一个 component fixture。 夹具可以访问 debugElement,这将使您能够访问组件夹具的内部。

更改检测不会自动完成,因此您将在夹具上调用 detectChanges 来告诉 Angular 运行更改检测。

async 包装测试的回调函数或 beforeEach 的第一个参数允许 Angular 执行异步编译并等待 async 块内的内容准备好之前继续。

了解测试

第一个测试名为 should create the app,它使用 expect 检查是否存在具有 toBeTruthy() 的组件。

第二个测试名为 should have as title 'angular-unit-test-example',它使用 expect 来检查 app.title 值是否等于字符串 'angular-unit-test-example'toEqual()

第三个测试名为 should render title,它使用 expect 来检查文本 'angular-unit-test-example app is running!'toContain() 的编译代码。

在您的终端中,运行以下命令:

ng test

所有三个测试都将运行并显示测试结果:

Output3 specs, 0 failures, randomized with seed 84683
AppComponent
* should have as title 'angular-unit-test-example'
* should create the app
* should render title

目前所有三个测试都通过了。

第 2 步 — 构建示例组件

让我们创建一个增加或减少值的组件。

在代码编辑器中打开 app.component.ts 并将以下代码行替换为 incrementdecrement 逻辑:

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 {
  value = 0;
  message!: string;

  increment() {
    if (this.value < 15) {
      this.value += 1;
      this.message = '';
    } else {
      this.message = 'Maximum reached!';
    }
  }

  decrement() {
    if (this.value > 0) {
      this.value -= 1;
      this.message = '';
    } else {
      this.message = 'Minimum reached!';
    }
  }
}

在代码编辑器中打开 app.component.html 并将内容替换为以下代码:

src/app/app.component.html

<h1>{{ value }}</h1>

<hr>

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

<button (click)="decrement()" class="decrement">Decrement</button>

<p class="message">
  {{ message }}
</p>

此时,您应该已经有了 app.component.tsapp.component.html 的修订版本。

第 3 步 — 构建测试套件

使用您的代码编辑器重新访问 app.component.spec.ts 并将其替换为以下代码行:

src/app/app.component.spec.ts

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './app.component';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let debugElement: DebugElement;

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

    fixture = TestBed.createComponent(AppComponent);
    debugElement = fixture.debugElement;
  }));

  it('should increment and decrement value', () => {
    fixture.componentInstance.increment();
    expect(fixture.componentInstance.value).toEqual(1);

    fixture.componentInstance.decrement();
    expect(fixture.componentInstance.value).toEqual(0);
  });

  it('should increment value in template', () => {
    debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    fixture.detectChanges();

    const value = debugElement.query(By.css('h1')).nativeElement.innerText;

    expect(value).toEqual('1');
  });

  it('should stop at 0 and show minimum message', () => {
    debugElement
      .query(By.css('button.decrement'))
      .triggerEventHandler('click', null);

    fixture.detectChanges();

    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;

    expect(fixture.componentInstance.value).toEqual(0);
    expect(message).toContain('Minimum');
  });

  it('should stop at 15 and show maximum message', () => {
    fixture.componentInstance.value = 15;
    debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);
      
    fixture.detectChanges();

    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;

    expect(fixture.componentInstance.value).toEqual(15);
    expect(message).toContain('Maximum');
  });
});

我们直接在 beforeEach 块中分配 fixturedebugElement 因为我们所有的测试都需要这些。 我们还通过从 @angular/core/testing 导入 ComponentFixture 和从 @angular/core 导入 DebugElement 来强类型化它们。

在我们的第一个测试中,我们调用组件实例本身的方法。

在剩下的测试中,我们使用我们的 DebugElement 来触发按钮点击。 注意 DebugElement 有一个 query 方法接受一个谓词。 在这里,我们使用 By 实用程序及其 css 方法在模板中查找特定元素。 DebugElement 还有一个 nativeElement 方法,用于直接访问 DOM。

我们还在最后 3 个测试中使用 fixture.detectChanges 来指示 Angular 在使用 Jasmine 的 expect 进行断言之前运行更改检测。

完成更改后,从终端运行 ng test 命令:

ng test

这将以监视模式启动 Karma,因此您的测试将在每次文件更改时重新编译。

Output4 specs, 0 failures, randomized with seed 27239
AppComponent
* should increment value in template
* should increment and decrement value
* should stop at 0 and show minimum message
* should stop at 15 and show maximum message

所有四项测试都将通过。

结论

在本文中,您将了解如何使用 Jasmine 和 Karma 在 Angular 中编写和运行单元测试。 现在您已经了解了主要的 Angular 测试实用程序,并且可以开始为简单组件编写测试了。

继续学习测试具有依赖关系的组件、测试服务以及使用 模拟、存根 和间谍。

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