Основы unit-тестирования в Angular
Development, Java | Комментировать запись
Если ваш проект был создан с помощью Angular CLI, у вас есть все для того, чтобы вы начали писать тесты, используя Jasmine в качестве среды тестирования и Karma для их запуска.
Читайте также: Шпаргалка по Angular CLI: основные команды и флаги
Также Angular предоставляет такие утилиты, как TestBed и async, чтобы упростить тестирование асинхронного кода, компонентов, директив или сервисов.
В этой статье мы поговорим о написании и запуске unit-тестов в Angular с помощью Jasmine и Karma.
Требования
- Локальная установка Node.js, которую можно получить, следуя инструкциям для вашего дистрибутива: Mac OS, Ubuntu, CentOS, Debian.
- Базовое знакомство с настройкой проекта 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. Откройте этот файл и проверьте его содержимое:
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?
Во-первых, нам нужно обсудить несколько вещей, которые важно знать о Jasmine:
- блоки describe определяют набор тестов, каждый такой блок предназначен для отдельного теста.
- beforeEach запускается перед каждым тестом и используется в части setup.
- afterEach запускается после каждого теста и используется в teardown.
- также можно использовать beforeAll и afterAll, они запускаются один раз до или после всех тестов.
- проверить утверждение в Jasmine можно с помощью expect и сопоставителей типа toBeDefined, toBeTruthy, toContain, toEqual, toThrow, toBeNull, … Например: expect(myValue).toBeGreaterThan(3);
- сделать утверждение отрицательным можно с помощью not: expect(myValue).not.toBeGreaterThan(3);
- также можно писать пользовательские сопоставители.
TestBed — основная утилита для специального тестирования Angular. Мы будем использовать TestBed.configureTestingModule в блоке beforeEach; ему можно присвоить объект со значениями, аналогичными обычному NgModule для declarations, providers и imports. Затем вы можете связать вызов compileComponents, чтобы Angular мог скомпилировать объявленные компоненты.
Вы можете создать component fixture с помощью TestBed.createComponent. Фикстуры имеют доступ к debugElement, который, в свою очередь, даст вам доступ к внутренним компонентам фикстуры.
Изменения не отслеживаются автоматически, поэтому нам нужно вызвать detectChanges для фикстуры, чтобы Angular запустил обнаружение изменений.
Обертывание callback функции теста или первого аргумента beforeEach с помощью 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
Когда все три теста будут выполнены, вы увидите результат:
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 в редакторе кода и замените следующие строки кода логикой increment и decrement:
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 в редакторе кода и замените содержимое следующим кодом:
<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 в редакторе кода и замените его следующими строками кода:
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');
});
});
Мы назначаем фикстуру и debugElement непосредственно в блоке beforeEach, потому что они нужны всем нашим тестам. Также мы строго типизируем их, импортируя ComponentFixture из @angular/core/testing и DebugElement из @angular/core.
В нашем первом тесте мы вызываем методы самого экземпляра компонента.
Оставшиеся тесты мы используем DebugElement для запуска нажатия кнопок. Обратите внимание, у DebugElement есть метод query, который принимает предикат. Здесь мы используем утилиту By и ее метод css для поиска определенного элемента в шаблоне. DebugElement также имеет метод nativeElement для прямого доступа к DOM.
Кроме того, в последних 3 тестах мы используем fixture.detectChanges, чтобы Angular мог запускать обнаружение изменений, прежде чем он выполнит наши утверждения с помощью expect.
После внесения изменений запустите команду ng test:
ng test
Это запустит Karma в режиме просмотра, поэтому ваши тесты будут перекомпилироваться каждый раз при изменении файла.
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
Все четыре теста будут пройдены успешно.
Заключение
В этой статье вы узнали, как писать и запускать unit-тесты в Angular с помощью Jasmine и Karma. Теперь, когда вы знакомы с основными утилитами тестирования Angular, вы можете начать писать тесты для простых компонентов.
Продолжайте обучение, тестируя компоненты с зависимостями, сервисами и т.д.
Вы также можете обратиться к официальной документации за подробным руководством по тестированию Angular.
Tags: Angular, Jasmine, Karma