Статьи

Тестирование компонентов в угловых условиях с использованием жасмина: часть 1

Конечный продукт
Что вы будете создавать

Разработка через тестирование — это практика программирования, которую проповедуют и продвигают все сообщества разработчиков на планете. И все же это рутина, которая в значительной степени игнорируется разработчиком при изучении новой среды. Написание модульных тестов с первого дня поможет вам написать лучший код, с легкостью обнаруживать ошибки и поддерживать лучший рабочий процесс разработки.

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
Снимок экрана интерфейса приложения
Вид компонента AddPaste
Компонент ViewPaste в действии, использующий модальный загрузчик
Вид компонента ViewPaste

В этом уроке вы узнаете, как:

  • настроить жасмин и карму
  • создать класс 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 внутри компонентов и тестов. Вы можете создать его, используя 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 чтобы убедиться, что все тесты зеленые.

Создайте сервис, используя приведенную ниже команду.

1
ng generate service pastebin

PastebinService будет содержать логику для отправки HTTP-запросов на сервер; однако у нас нет серверного API для приложения, которое мы создаем. Поэтому мы собираемся смоделировать взаимодействие с сервером, используя модуль, известный как InMemoryWebApiModule .

Установите 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 содержит логику для создания новых паст.

Идем дальше и генерируем компоненты, используя 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

Нам нужно написать тесты со следующими ожиданиями.

  • Компонент 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

Логика компонента должна соответствовать следующим требованиям.

  • Шаблон компонента 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 в нашем проекте

В следующем уроке мы создадим новые компоненты, напишем больше тестовых компонентов с входами и выходами, службами и маршрутами. Оставайтесь с нами для второй части серии. Поделитесь своими мыслями через комментарии.