如何开始Angular的单元测试

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

介绍

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

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

在本文中,您将了解如何使用 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 部分。
  • 您还可以使用 beforeAll 和 afterAll,它们在所有测试之前或之后运行一次。
  • 您使用 expect 并使用 toBeDefined、toBeTruthy、toContain、toEqual、[ 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 并将以下代码行替换为 increment 和 decrement 逻辑:

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.ts 和 app.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 块中分配 fixture 和 debugElement 因为我们所有的测试都需要这些。 我们还通过从 @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 测试指南。