如何开始对 Angular 进行单元测试

介绍

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

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

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

先决条件

要完成本教程,您需要:

本教程已通过 Node v16.2.0、npmv7.15.1 和@angular/corev12.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!');
  });
});

了解茉莉花

首先,了解 Jasmine 的一些重要事项:

  • describe块定义了一个测试套件,每个it块用于一个单独的测试。
  • beforeEach在每次测试之前运行并用于测试的setup一部分。
  • afterEach在每次测试之后运行并用于测试的teardown一部分。
  • 您也可以使用beforeAlland afterAll,它们在所有测试之前或之后运行一次。
  • 您在 Jasmine 中expect使用匹配器测试断言,toBeDefined, toBeTruthy, toContain, toEqual, toThrow, toBeNull, … 例如:expect(myValue).toBeGreaterThan(3);
  • 您可以使用以下方法进行否定断言notexpect(myValue).not.toBeGreaterThan(3);
  • 您还可以定义自定义匹配器。

TestBed是可用于特定于 Angular 的测试的主要实用程序。您将使用TestBed.configureTestingModule在测试套件的beforeEach块,并给它有类似值的一个对象作为一个普通NgModuledeclarationsprovidersimports然后你可以链接一个调用来compileComponents告诉 Angular 编译声明的组件。

您可以创建一个component fixtureTestBed.createComponent. 夹具可以访问 a debugElement,这将使您可以访问组件夹具的内部结构。

更改检测不会自动完成,因此您将调用detectChanges一个装置来告诉 Angular 运行更改检测。

将测试的回调函数或beforeEachwith的第一个参数包装起来,async允许 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

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

Output
3 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.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');
  });
});

我们直接在块中分配fixturedebugElementbeforeEach因为我们所有的测试都需要这些。我们还通过导入ComponentFixturefrom@angular/core/testingDebugElementfrom 来对它们进行强类型化@angular/core

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

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

我们还在fixture.detectChanges最后 3 个测试中使用来指示 Angular 在对 Jasmine 的expect.

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

  • ng test

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

Output
4 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 测试指南。

觉得文章有用?

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