Основы 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