Разработка через тестирование — это практика программирования, которую проповедуют и продвигают все сообщества разработчиков на планете. И все же это рутина, которая в значительной степени игнорируется разработчиком при изучении новой среды. Написание модульных тестов с первого дня поможет вам написать лучший код, с легкостью обнаруживать ошибки и поддерживать лучший рабочий процесс разработки.
Тест-ориентированная разработка в Angular
Angular, являясь полноценной платформой для фронт-энда, имеет собственный набор инструментов для тестирования. В этом уроке мы будем использовать следующие инструменты:
- Жасмин Фреймворк . Jasmine — популярная основанная на поведении среда тестирования для JavaScript. С помощью Jasmine вы можете писать более выразительные и понятные тесты. Вот пример для начала.
1
2
3
|
it(‘should have a defined component’, () => {
expect(component).toBeDefined();
});
|
- Карма Тест Бегун . Карма — это инструмент, который позволяет вам протестировать ваше приложение в нескольких браузерах. Карма имеет плагины для браузеров, таких как Chrome, Firefox, Safari и многих других. Но я предпочитаю использовать безголовый браузер для тестирования. Безголовый браузер не имеет графического интерфейса, и таким образом вы можете сохранить результаты теста в своем терминале. В этом руководстве мы настроим Karma для работы с Chrome и, необязательно, безголовой версией Chrome.
-
Угловые Утилиты Тестирования . Утилиты углового тестирования предоставляют вам библиотеку для создания тестовой среды для вашего приложения. Классы, такие как
TestBed
иComponentFixtures
и вспомогательные функции, такие какasync
иfakeAsync
являются частью пакета@angular/core/testing
. Знакомство с этими утилитами необходимо, если вы хотите написать тесты, которые показывают, как ваши компоненты взаимодействуют с их собственным шаблоном, сервисами и другими компонентами.
Мы не будем рассматривать функциональные тесты с использованием Protractor в этом руководстве. Protractor — это популярная сквозная среда тестирования, которая взаимодействует с пользовательским интерфейсом приложения с помощью реального браузера.
В этом уроке мы больше заботимся о тестировании компонентов и логике компонента. Тем не менее, мы напишем пару тестов, которые демонстрируют базовое взаимодействие с пользовательским интерфейсом с использованием среды Jasmine.
Наша цель
Цель данного руководства — создать интерфейс для приложения Pastebin в среде разработки, управляемой тестами. В этом уроке мы будем следовать популярной мантре TDD, которая называется «красный / зеленый / рефакторинг». Мы напишем тесты, которые изначально не пройдут (красный), а затем поработаем над кодом нашего приложения, чтобы они прошли (зеленый). Мы проведем рефакторинг нашего кода, когда он начнет вонять, что означает, что он становится раздутым и уродливым.
Мы будем писать тесты для компонентов, их шаблонов, сервисов и класса Pastebin. Изображение ниже иллюстрирует структуру нашего приложения Pastebin. Элементы, выделенные серым цветом, будут обсуждаться во второй части урока.
В первой части серии мы сосредоточимся исключительно на настройке среды тестирования и написании базовых тестов для компонентов. Angular — это основанная на компонентах структура; поэтому неплохо бы потратить некоторое время на знакомство с написанием тестов для компонентов. Во второй части серии мы напишем более сложные тесты для компонентов, компонентов с входными данными, маршрутизируемых компонентов и сервисов. К концу серии у нас будет полнофункциональное приложение Pastebin, которое выглядит следующим образом.
В этом уроке вы узнаете, как:
- настроить жасмин и карму
- создать класс Pastebin, который представляет отдельную вставку
- создать голый сервис PastebinService
- создать два компонента, Pastebin и AddPaste
- написать юнит-тесты
Весь код для учебника доступен на Github.
1
|
https://github.com/blizzerand/pastebin-angular
|
Клонируйте репозиторий и не стесняйтесь проверять код, если у вас есть сомнения на любом этапе этого урока. Давайте начнем!
Настройка Жасмин и Карма
Разработчики Angular упростили нам настройку среды тестирования. Для начала нам нужно сначала установить Angular. Я предпочитаю использовать Angular-CLI. Это универсальное решение, которое заботится о создании, создании, создании и тестировании вашего проекта Angular.
1
|
ng new Pastebin
|
Вот структура каталогов, созданная Angular-CLI.
Поскольку наши интересы больше связаны с аспектами тестирования в Angular, нам нужно искать два типа файлов.
karma.conf.js — это файл конфигурации для запуска тестов Karma и единственный файл конфигурации, который нам понадобится для написания модульных тестов на Angular. По умолчанию Chrome является браузером-пусковой установкой по умолчанию, используемой Кармой для записи тестов. Мы создадим пользовательский модуль запуска для запуска безголового Chrome и добавим его в массив browsers
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
/*karma.conf.js*/
browsers: [‘Chrome’,’ChromeNoSandboxHeadless’],
customLaunchers: {
ChromeNoSandboxHeadless: {
base: ‘Chrome’,
flags: [
‘—no-sandbox’,
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
‘—headless’,
‘—disable-gpu’,
// Without a remote debugging port, Google Chrome exits immediately.
‘ —remote-debugging-port=9222’,
],
},
},
|
Другой тип файлов, на который нам нужно обратить внимание, это все, что заканчивается на .spec.ts
. По соглашению тесты, написанные на жасмине, называются спецификациями. Все тестовые спецификации должны быть расположены в каталоге src/app/
потому что именно здесь Karma ищет тестовые спецификации. Если вы создаете новый компонент или службу, важно, чтобы вы поместили свои тестовые спецификации в тот же каталог, в котором находится код для компонента или службы.
Команда ng new
создала файл app.component.spec.ts
для нашего app.component.ts
. Не стесняйтесь открыть его и внимательно посмотрите на тесты Жасмин в Angular. Даже если код не имеет никакого смысла, это нормально. Мы оставим AppComponent таким, какой он есть сейчас, и будем использовать его для размещения маршрутов на более позднем этапе учебного курса.
Создание класса Pastebin
Нам нужен класс Pastebin для моделирования нашего Pastebin внутри компонентов и тестов. Вы можете создать его, используя Angular-CLI.
1
|
ng generate class Pastebin
|
Добавьте следующую логику в Pastebin.ts:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
export class Pastebin {
id: number;
title: string;
language: string;
paste: string;
constructor(values: Object = {}) {
Object.assign(this, values);
}
}
export const Languages = [«Ruby»,»Java», «JavaScript», «C», «Cpp»];
|
Мы определили класс Pastebin, и каждый экземпляр этого класса будет иметь следующие свойства:
-
id
-
title
-
language
-
paste
Создайте еще один файл с именем pastebin.spec.ts
для набора тестов.
01
02
03
04
05
06
07
08
09
10
|
/* pastebin.spec.ts */
//import the Pastebin class
import { Pastebin } from ‘./pastebin’;
describe(‘Pastebin’, () => {
it(‘should create an instance of Pastebin’,() => {
expect(new Pastebin()).toBeTruthy();
});
})
|
Набор тестов начинается с блока describe
, который является глобальной функцией Jasmine, которая принимает два параметра. Первый параметр — это название набора тестов, а второй — его фактическая реализация. Спецификации определяются с использованием функции it
которая принимает два параметра, аналогичных describe
блоке description.
Несколько спецификаций (блоки) могут быть вложены в набор тестов ( describe
блок). Тем не менее, убедитесь, что названия наборов тестов названы так, чтобы они были однозначными и более читаемыми, поскольку они должны служить документацией для читателя.
Ожидания, реализованные с использованием функции expect
, используются Жасмином для определения, должна ли спецификация пройти или потерпеть неудачу. Функция expect
принимает параметр, который известен как фактическое значение. Затем он связан с другой функцией, которая принимает ожидаемое значение. Эти функции называются функциями сопоставления, и мы будем использовать функции сопоставления, такие как toBeTruthy()
, toBeDefined()
, toBe()
и toContain()
в этом руководстве.
1
|
expect(new Pastebin()).toBeTruthy();
|
Итак, с помощью этого кода мы создали новый экземпляр класса Pastebin и ожидаем, что он будет истинным. Давайте добавим еще одну спецификацию, чтобы подтвердить, что модель Pastebin работает так, как задумано.
01
02
03
04
05
06
07
08
09
10
11
12
|
it(‘should accept values’, () => {
let pastebin = new Pastebin();
pastebin = {
id: 111,
title: «Hello world»,
language: «Ruby»,
paste: ‘print «Hello»‘,
}
expect(pastebin.id).toEqual(111);
expect(pastebin.language).toEqual(«Ruby»);
expect(pastebin.paste).toEqual(‘print «Hello»‘);
});
|
Мы создали экземпляр класса Pastebin и добавили несколько ожиданий в нашу тестовую спецификацию. Запустите ng test
чтобы убедиться, что все тесты зеленые.
Создание сервиса Bare-Bones
Создайте сервис, используя приведенную ниже команду.
1
|
ng generate service pastebin
|
PastebinService
будет содержать логику для отправки HTTP-запросов на сервер; однако у нас нет серверного API для приложения, которое мы создаем. Поэтому мы собираемся смоделировать взаимодействие с сервером, используя модуль, известный как InMemoryWebApiModule .
Настройка Angular-in-Memory-Web-API
Установите angular-in-memory-web-api
через npm:
1
|
npm install angular-in-memory-web-api —save
|
Обновите AppModule этой версией.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/* app.module.ts */
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
//Components
import { AppComponent } from ‘./app.component’;
//Service for Pastebin
import { PastebinService } from «./pastebin.service»;
//Modules used in this tutorial
import { HttpModule } from ‘@angular/http’;
//In memory Web api to simulate an http server
import { InMemoryWebApiModule } from ‘angular-in-memory-web-api’;
import { InMemoryDataService } from ‘./in-memory-data.service’;
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
HttpModule,
InMemoryWebApiModule.forRoot(InMemoryDataService),
],
providers: [PastebinService],
bootstrap: [AppComponent]
})
export class AppModule { }
|
Создайте InMemoryDataService
который реализует InMemoryDbService
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
/*in-memory-data.service.ts*/
import { InMemoryDbService } from ‘angular-in-memory-web-api’;
import { Pastebin } from ‘./pastebin’;
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const pastebin:Pastebin[] = [
{ id: 0, title: «Hello world Ruby», language: «Ruby», paste: ‘puts «Hello World»‘ },
{id: 1, title: «Hello world C», language: «C», paste: ‘printf(«Hello world»);’},
{id: 2, title: «Hello world CPP», language: «C++», paste: ‘cout<<«Hello world»;’},
{id: 3, title: «Hello world Javascript», language: «JavaScript», paste: ‘console.log(«Hello world»)’}
];
return {pastebin};
}
}
|
Здесь pastebin
— это массив примеров паст, которые будут возвращены или обновлены, когда мы выполним действие HTTP, такое как http.get
или http.post
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/*pastebin.service.ts */
import { Injectable } from ‘@angular/core’;
import { Pastebin } from ‘./pastebin’;
import { Http, Headers } from ‘@angular/http’;
import ‘rxjs/add/operator/toPromise’;
@Injectable()
export class PastebinService {
// The project uses InMemoryWebApi to handle the Server API.
// Here «api/pastebin» simulates a Server API url
private pastebinUrl = «api/pastebin»;
private headers = new Headers({‘Content-Type’: «application/json»});
constructor(private http: Http) { }
// getPastebin() performs http.get() and returns a promise
public getPastebin():Promise<any> {
return this.http.get(this.pastebinUrl)
.toPromise()
.then(response => response.json().data)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error(‘An error occurred’, error);
return Promise.reject(error.message || error);
}
}
|
Метод getPastebin()
создает запрос HTTP.get и возвращает обещание, которое разрешается в массив объектов Pastebin, возвращаемых сервером.
Если при запуске спецификации вы получаете сообщение об отсутствии провайдера HTTP , вам нужно импортировать HTTPModule в соответствующий файл спецификации.
Начало работы с компонентами
Компоненты являются самым основным строительным блоком пользовательского интерфейса в приложении Angular. Приложение Angular — это дерево компонентов Angular.
— угловая документация
Как было отмечено ранее в разделе «Обзор», в этом руководстве мы будем работать над двумя компонентами: PastebinComponent
и AddPasteComponent
. Компонент Pastebin состоит из структуры таблицы, в которой перечислены все вставки, полученные с сервера. Компонент AddPaste содержит логику для создания новых паст.
Разработка и тестирование компонента Pastebin
Идем дальше и генерируем компоненты, используя Angular-CLI.
1
|
ng g component —spec=false Pastebin
|
Опция --spec=false
указывает Angular-CLI не создавать файл спецификации. Это потому, что мы хотим написать модульные тесты для компонентов с нуля. Создайте файл pastebin.component.spec.ts
внутри папки pastebin-component .
Вот код для pastebin.component.spec.ts
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import { TestBed, ComponentFixture, async } from ‘@angular/core/testing’;
import { DebugElement } from ‘@angular/core’;
import { PastebinComponent } from ‘./pastebin.component’;
import { By } from ‘@angular/platform-browser’;
import { Pastebin, Languages } from ‘../pastebin’;
//Modules used for testing
import { HttpModule } from ‘@angular/http’;
describe(‘PastebinComponent’, () => {
//Typescript declarations.
let comp: PastebinComponent;
let fixture: ComponentFixture<PastebinComponent>;
let de: DebugElement;
let element: HTMLElement;
let mockPaste: Pastebin[];
// beforeEach is called once before every `it` block in a test.
// Use this to configure to the component, inject services etc.
beforeEach(()=> {
TestBed.configureTestingModule({
declarations: [ PastebinComponent ], // declare the test component
imports: [ HttpModule],
});
fixture = TestBed.createComponent(PastebinComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css(‘.pastebin’));
element = de.nativeElement;
});
})
|
Здесь много чего происходит. Давайте разберемся и возьмем по одной части за раз. Внутри блока describe
мы объявили некоторые переменные, а затем использовали функцию beforeEach
. beforeEach()
— это глобальная функция, предоставляемая Jasmine, и, как следует из названия, она вызывается один раз перед каждой спецификацией в блоке description, в котором она вызывается.
1
2
3
4
|
TestBed.configureTestingModule({
declarations: [ PastebinComponent ], // declare the test component
imports: [ HttpModule],
});
|
Класс TestBed
является частью утилит тестирования Angular и создает модуль тестирования, аналогичный @NgModule
класса @NgModule
. Кроме того, вы можете настроить TestBed
с configureTestingModule
метода configureTestingModule
. Например, вы можете создать тестовую среду для вашего проекта, которая эмулирует фактическое приложение Angular, а затем вы можете извлечь компонент из вашего прикладного модуля и повторно присоединить его к этому тестовому модулю.
1
2
3
4
|
fixture = TestBed.createComponent(PastebinComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css(‘div’));
element = de.nativeElement;
|
Из угловой документации :
Метод
createComponent
возвращаетComponentFixture
, дескриптор тестовой среды, окружающей созданный компонент. Устройство обеспечивает доступ к самому экземпляру компонента и кDebugElement
, который является дескриптором элемента DOM компонента.
Как упоминалось выше, мы создали фиксатор PastebinComponent
а затем использовали этот фиксатор для создания экземпляра компонента. Теперь мы можем получить доступ к свойствам и методам компонента внутри наших тестов, вызвав comp.property_name
. Поскольку прибор также предоставляет доступ к debugElement
, теперь мы можем запрашивать элементы и селекторы DOM.
Есть проблема с нашим кодом, о которой мы еще не думали. Наш компонент имеет внешний шаблон и файл CSS. Извлечение и чтение их из файловой системы является асинхронным действием, в отличие от остального кода, который является синхронным.
Angular предлагает вам функцию async()
которая заботится обо всем асинхронном. Что делает асинхронный, так это отслеживает все асинхронные задачи внутри него, скрывая при этом сложность асинхронного выполнения от нас. Таким образом, теперь у нас будет две функции beforeEach: асинхронная beforeEach()
и синхронная beforeEach()
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
/* pastebin.component.spec.ts */
// beforeEach is called once before every `it` block in a test.
// Use this to configure to the component, inject services etc.
beforeEach(async(() => { //async before is used for compiling external templates which is any async activity
TestBed.configureTestingModule({
declarations: [ PastebinComponent ], // declare the test component
imports: [ HttpModule],
})
.compileComponents();
}));
beforeEach(()=> { //And here is the synchronous async function
fixture = TestBed.createComponent(PastebinComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css(‘.pastebin’));
element = de.nativeElement;
});
|
Мы еще не написали никаких тестовых спецификаций. Тем не менее, это хорошая идея, чтобы заранее составить план спецификаций. Изображение ниже изображает приблизительный дизайн компонента Pastebin.
Нам нужно написать тесты со следующими ожиданиями.
- Компонент Pastebin должен существовать.
- Свойство title компонента должно отображаться в шаблоне.
- Шаблон должен иметь HTML-таблицу для отображения существующих вставок.
-
pastebinService
внедряется в компонент, и его методы доступны. - Никакие
onInit()
не должны отображаться, покаonInit()
не вызван. - Пасты не отображаются до тех пор, пока не будет выполнено обещание в нашем Сервисе.
Первые три теста легко реализовать.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
it(‘should have a Component’,()=> {
expect(comp).toBeTruthy();
});
it(‘should have a title’, () => {
comp.title = ‘Pastebin Application’;
fixture.detectChanges();
expect(element.textContent).toContain(comp.title);
})
it(‘should have a table to display the pastes’, () => {
expect(element.innerHTML).toContain(«thead»);
expect(element.innerHTML).toContain(«tbody»);
})
|
В тестовой среде Angular не связывает автоматически свойства компонента с элементами шаблона. Вы должны явно вызывать fixture.detectChanges()
каждый раз, когда хотите связать свойство компонента с шаблоном. Запуск теста должен дать вам ошибку, потому что мы еще не объявили свойство title внутри нашего компонента.
1
|
title: string = «Pastebin Application»;
|
Не забудьте обновить шаблон базовой структурой таблицы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<div class = «pastebin»>
<h2> {{title}}</h2>
<table id=»table» class=»table table-hover table-striped»>
<thead>
<tr>
<th> id </th>
<th> Title </th>
<th> Language </th>
<th> Code </th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
|
Что касается остальных случаев, нам нужно внедрить Pastebinservice
и написать тесты, которые имеют дело с взаимодействием компонента с сервисом. Реальный сервис может выполнять вызовы на удаленный сервер, и внедрение его в необработанном виде будет трудоемкой и сложной задачей.
Вместо этого мы должны написать тесты, которые фокусируются на том, взаимодействует ли компонент со службой должным образом. Мы добавим спецификации, которые getPastebin()
за pastebinService
и его getPastebin()
.
Во-первых, импортируйте PastebinService
в наш набор тестов.
1
|
import { PastebinService } from ‘../pastebin.service’;
|
Затем добавьте его в массив providers
внутри TestBed.configureTestingModule()
.
1
2
3
4
|
TestBed.configureTestingModule({
declarations:[ CreateSnippetComponent],
providers: [ PastebinService ],
});
|
Приведенный ниже код создает шпион Jasmine, который предназначен для отслеживания всех вызовов метода getPastebin()
и возврата обещания, которое немедленно разрешается в mockPaste
.
1
2
3
4
5
6
7
|
//The real PastebinService is injected into the component
let pastebinService = fixture.debugElement.injector.get(PastebinService);
mockPaste = [
{ id:1, title: «Hello world», language: «Ruby», paste: «puts ‘Hello’» }];
spy = spyOn(pastebinService, ‘getPastebin’)
.and.returnValue(Promise.resolve(mockPaste));
|
Шпион не заботится о деталях реализации реального сервиса, но вместо этого обходит любой вызов фактического getPastebin()
. Более того, все удаленные вызовы, getPastebin()
внутри getPastebin()
, игнорируются нашими тестами. Мы будем писать отдельные модульные тесты для сервисов Angular во второй части руководства.
Добавьте следующие тесты в pastebin.component.spec.ts
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
it(‘should not show the pastebin before OnInit’, () => {
this.tbody = element.querySelector(«tbody»);
//Try this without the ‘replace(\s\s+/g,»)’ method and see what happens
expect(this.tbody.innerText.replace(/\s\s+/g, »)).toBe(«», «tbody should be empty»);
expect(spy.calls.any()).toBe(false, «Spy shouldn’t be yet called»);
});
it(‘should still not show pastebin after component initialized’, () => {
fixture.detectChanges();
// getPastebin service is async, but the test is not.
expect(this.tbody.innerText.replace(/\s\s+/g, »)).toBe(«», ‘tbody should still be empty’);
expect(spy.calls.any()).toBe(true, ‘getPastebin should be called’);
});
it(‘should show the pastebin after getPastebin promise resolves’, async() => {
fixture.detectChanges();
fixture.whenStable().then( () => {
fixture.detectChanges();
expect(comp.pastebin).toEqual(jasmine.objectContaining(mockPaste));
expect(element.innerText.replace(/\s\s+/g, ‘ ‘)).toContain(mockPaste[0].title);
});
})
|
Первые два теста являются синхронными тестами. Первая спецификация проверяет, остается ли innerText
элемента div
пустым, пока компонент не инициализирован. Второй аргумент функции соответствия Jasmine является необязательным и отображается при сбое теста. Это полезно, когда в спецификации есть несколько операторов ожидаемого.
Во второй спецификации компонент инициализируется (потому что fixture.detectChanges()
), и ожидается, что шпион также будет вызван, но шаблон не должен обновляться. Хотя шпион возвращает обещанное обещание, mockPaste
еще не доступен. Он не должен быть доступен, если тест не является асинхронным.
В третьем тесте используется функция async()
описанная ранее, для запуска теста в зоне асинхронного теста. async()
используется, чтобы сделать синхронный тест асинхронным. fixture.whenStable()
вызывается, когда все ожидающие асинхронные действия дополняются, а затем fixture.detectChanges()
второй раунд fixture.detectChanges()
для обновления DOM с новыми значениями. Ожидание в финальном тесте гарантирует, что наша DOM будет обновлена со значениями mockPaste
.
Чтобы пройти тесты, нам нужно обновить наш pastebin.component.ts
следующим кодом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/*pastebin.component.ts*/
import { Component, OnInit } from ‘@angular/core’;
import { Pastebin } from ‘../pastebin’;
import { PastebinService } from ‘../pastebin.service’;
@Component({
selector: ‘app-pastebin’,
templateUrl: ‘./pastebin.component.html’,
styleUrls: [‘./pastebin.component.css’]
})
export class PastebinComponent implements OnInit {
title: string = «Pastebin Application»;
pastebin: any = [];
constructor(public pastebinServ: PastebinService) { }
//loadPastebin() is called on init
ngOnInit() {
this.loadPastebin();
}
public loadPastebin() {
//invokes pastebin service’s getPastebin() method and stores the response in `pastebin` property
this.pastebinServ.getPastebin().then(pastebin => this.pastebin = pastebin);
}
}
|
Шаблон также должен быть обновлен.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<!— pastebin.component.html —>
<div class = «pastebin»>
<h2> {{title}}</h2>
<table id=»table» class=»table table-hover table-striped»>
<thead>
<tr>
<th> id </th>
<th> Title </th>
<th> Language </th>
<th> Code </th>
</tr>
</thead>
<tbody>
<tr *ngFor=»let paste of pastebin»>
<td> {{paste.id}} </td>
<td> {{paste.title}} </td>
<td> {{paste.language}} </td>
<td> View code </td>
</tr>
</tbody>
<!— <app-add-paste (addPasteSuccess)= ‘onAddPaste($event)’> </app-add-paste> —>
</table>
</div>
|
Добавление новой вставки
Создайте компонент AddPaste, используя Angular-CLI. На рисунке ниже изображен дизайн компонента AddPaste.
Логика компонента должна соответствовать следующим требованиям.
- Шаблон компонента AddPaste должен иметь кнопку « Создать вставку» .
- При нажатии кнопки « Создать вставку» должно появиться модальное поле с идентификатором «source-modal».
- Действие click также должно обновить свойство
showModal
компонента доtrue
. (showModal
— это логическое свойство, которое становится истинным, когда отображается модальное, и ложным, когда модальное закрыто.) - Нажатие кнопки сохранения должно вызвать метод
addPaste()
службы Pastebin. - Нажатие на кнопку закрытия должно удалить идентификатор ‘source-modal’ из DOM и
showModal
свойствоshowModal
наfalse
.
Мы разработали первые три теста для вас. Посмотрите, сможете ли вы пройти тесты самостоятельно.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
describe(‘AddPasteComponent’, () => {
let component: AddPasteComponent;
let fixture: ComponentFixture<AddPasteComponent>;
let de: DebugElement;
let element: HTMLElement;
let spy: jasmine.Spy;
let pastebinService: PastebinService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AddPasteComponent ],
imports: [ HttpModule, FormsModule ],
providers: [ PastebinService ],
})
.compileComponents();
}));
beforeEach(() => {
//initialization
fixture = TestBed.createComponent(AddPasteComponent);
pastebinService = fixture.debugElement.injector.get(PastebinService);
component = fixture.componentInstance;
de = fixture.debugElement.query(By.css(‘.add-paste’));
element = de.nativeElement;
spy = spyOn(pastebinService, ‘addPaste’).and.callThrough();
//ask fixture to detect changes
fixture.detectChanges();
});
it(‘should be created’, () => {
expect(component).toBeTruthy();
});
it(‘should display the `create Paste` button’, () => {
//There should a create button in the template
expect(element.innerText).toContain(«create Paste»);
});
it(‘should not display the modal unless the button is clicked’, () => {
//source-model is an id for the modal.
expect(element.innerHTML).not.toContain(«source-modal»);
})
it(«should display the modal when ‘create Paste’ is clicked», () => {
let createPasteButton = fixture.debugElement.query(By.css(«button»));
//triggerEventHandler simulates a click event on the button object
createPasteButton.triggerEventHandler(«click»,null);
fixture.detectChanges();
expect(element.innerHTML).toContain(«source-modal»);
expect(component.showModal).toBeTruthy(«showModal should be true»);
})
})
|
DebugElement.triggerEventHandler()
— это единственное, что здесь DebugElement.triggerEventHandler()
. Он используется для запуска события нажатия на элементе кнопки, для которого он вызывается. Второй параметр — это объект события, и мы оставили его пустым, так как click()
компонента не ожидает его.
Резюме
Вот и все на сегодня. В этой первой статье мы узнали:
- как настроить и настроить Жасмин и Карма
- как написать базовые тесты для классов
- как проектировать и писать модульные тесты для компонентов
- как создать базовый сервис
- как использовать утилиты тестирования Angular в нашем проекте
В следующем уроке мы создадим новые компоненты, напишем больше тестовых компонентов с входами и выходами, службами и маршрутами. Оставайтесь с нами для второй части серии. Поделитесь своими мыслями через комментарии.