Spec-Zone.ru › Angular

Основы тестирования компонентов

Компонент, в отличие от всех остальных частей приложения Angular, объединяет HTML-шаблон и класс TypeScript. Компонент — это шаблон и класс, работающие вместе. Чтобы корректно протестировать компонент, нужно проверить, что они работают вместе так, как задумано.

Для таких тестов необходимо создать элемент-хозяин компонента в DOM браузера, как это делает Angular, и исследовать взаимодействие класса компонента с DOM, описанным его шаблоном.

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

Тестирование DOM компонента

Компонент — это нечто большее, чем просто его класс. Компонент взаимодействует с DOM и другими компонентами. По одним только классам нельзя понять, будет ли компонент корректно отображаться, реагировать на пользовательский ввод и жесты или интегрироваться с родительскими и дочерними компонентами.

  • Привязан ли Lightswitch.clicked() к чему-либо так, чтобы пользователь мог его вызвать?
  • Отображается ли Lightswitch.message?
  • Может ли пользователь действительно выбрать героя, отображаемого компонентом DashboardHero?
  • Отображается ли имя героя так, как ожидается (например, заглавными буквами)?
  • Отображается ли приветственное сообщение, заданное в шаблоне компонента Welcome?

Для описанных выше простых компонентов эти вопросы могут и не показаться сложными. Но многие компоненты сложно взаимодействуют с элементами DOM, описанными в их шаблонах, из-за чего HTML появляется и исчезает по мере изменения состояния компонента.

Чтобы ответить на подобные вопросы, необходимо создать DOM-элементы, связанные с компонентами, изучить DOM и убедиться, что состояние компонента отображается правильно в нужные моменты, а также имитировать взаимодействие пользователя с экраном, чтобы определить, приводит ли это взаимодействие к ожидаемому поведению компонента.

Для написания таких тестов вы будете использовать дополнительные возможности TestBed и другие вспомогательные средства тестирования.

Тесты, создаваемые CLI

По умолчанию CLI создает для вас исходный файл теста, когда вы просите его создать новый компонент.

Например, следующая команда CLI создает компонент Banner в папке app/banner (со встроенными шаблоном и стилями):

ng generate component banner --inline-template --inline-style

Кроме того, для компонента создается исходный файл теста banner.spec.ts, который выглядит так:

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Banner} from './banner';

describe('Banner', () => {
  let component: Banner;
  let fixture: ComponentFixture<Banner>;

  beforeEach(async () => {
    TestBed.configureTestingModule({});

    fixture = TestBed.createComponent(Banner);
    component = fixture.componentInstance;
    await fixture.whenStable();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Упрощение настройки

Только последние три строки этого файла действительно тестируют компонент, и все, что они делают, — проверяют, что Angular может создать компонент.

Остальная часть файла — это шаблонный код настройки, предусмотренный для более сложных тестов, которые могут понадобиться, если компонент разрастется и станет более сложным.

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

describe('Banner (minimal)', () => {
  it('should create', () => {
    const fixture = TestBed.createComponent(Banner);
    const component = fixture.componentInstance;
    expect(component).toBeDefined();
  });
});

Позже вы вызовете TestBed.configureTestingModule(), указав импорты, провайдеры и другие объявления в соответствии с потребностями тестирования. Необязательные методы override позволяют дополнительно настроить отдельные параметры конфигурации.

ПРИМЕЧАНИЕ: TestBed.compileComponents требуется только в том случае, если в тестируемых компонентах используются блоки @defer.

createComponent()

Настроив TestBed, вызовите его метод createComponent().

const fixture = TestBed.createComponent(Banner);

TestBed.createComponent() создает экземпляр компонента Banner, добавляет соответствующий элемент в DOM средства запуска тестов и возвращает ComponentFixture.

ВАЖНО: Не перенастраивайте TestBed после вызова createComponent.

Метод createComponent фиксирует текущее определение TestBed, запрещая дальнейшую настройку.

После этого нельзя вызывать другие методы настройки TestBed: ни configureTestingModule(), ни get(), ни один из методов override.... При попытке вызова TestBed выдаст ошибку.

ComponentFixture

ComponentFixture — это тестовая оболочка для взаимодействия с созданным компонентом и соответствующим ему элементом.

Получите экземпляр компонента через фикстуру и убедитесь с помощью проверки, что он существует:

const component = fixture.componentInstance;
expect(component).toBeDefined();

beforeEach()

По мере развития компонента вы будете добавлять новые тесты. Вместо того чтобы дублировать настройку TestBed для каждого теста, вынесите ее в beforeEach() и добавьте несколько вспомогательных переменных:

describe('Banner (with beforeEach)', () => {
  let component: Banner;
  let fixture: ComponentFixture<Banner>;

  beforeEach(async () => {
    fixture = TestBed.createComponent(Banner);
    component = fixture.componentInstance;

    await fixture.whenStable(); // necessary to wait for the initial rendering
  });

  it('should create', () => {
    expect(component).toBeDefined();
  });
});

ПОЛЕЗНО: Если дождаться первоначального рендеринга в beforeEach с помощью await fixture.whenStable, отдельные тесты будут синхронными.

Теперь добавьте тест, который получает элемент компонента из fixture.nativeElement и ищет ожидаемый текст.

it('should contain "banner works!"', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  expect(bannerElement.textContent).toContain('banner works!');
});

Создание функции настройки

Вместо beforeEach можно также создать функцию настройки и вызывать ее в каждом тесте. Преимущество функции настройки в том, что ее можно адаптировать с помощью параметров.

Вот пример того, как может выглядеть функция настройки:

function setup(providers?: StaticProviders[]): ComponentFixture<Banner> {
  TestBed.configureTestingModule({providers});
  return TestBed.createComponent(Banner);
}

nativeElement

Значение ComponentFixture.nativeElement имеет тип any. Позже вы познакомитесь с DebugElement.nativeElement, который также имеет тип any.

Во время компиляции Angular не может знать, каким HTML-элементом является nativeElement и является ли он HTML-элементом вообще. Приложение может выполняться на платформе, отличной от браузера, например на сервере или в среде Node.js, где у элемента может быть ограниченный API или он может отсутствовать совсем.

Тесты в этом руководстве предназначены для запуска в браузере, поэтому значение nativeElement всегда будет объектом HTMLElement или одного из его производных классов.

Зная, что это объект HTMLElement, воспользуйтесь стандартным HTML-свойством querySelector, чтобы исследовать дерево элементов глубже.

Вот еще один тест: он вызывает HTMLElement.querySelector, чтобы получить элемент абзаца и найти текст баннера:

it('should have <p> with "banner works!"', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  const p = bannerElement.querySelector('p')!;
  expect(p.textContent).toEqual('banner works!');
});

DebugElement

Фикстура Angular предоставляет прямой доступ к элементу компонента через fixture.nativeElement.

const bannerElement: HTMLElement = fixture.nativeElement;

На самом деле это вспомогательный метод, реализованный как fixture.debugElement.nativeElement.

const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;

Такой обходной путь для доступа к элементу выбран не случайно.

Свойства nativeElement зависят от среды выполнения. Тесты могут запускаться на платформе, отличной от браузера, где нет DOM или эмуляция DOM не поддерживает весь API HTMLElement.

Angular использует абстракцию DebugElement, чтобы безопасно работать на всех поддерживаемых платформах. Вместо создания дерева HTML-элементов Angular создает дерево DebugElement, оборачивающее нативные элементы платформы выполнения. Свойство nativeElement снимает обертку с DebugElement и возвращает объект элемента, специфичный для платформы.

Поскольку примеры тестов в этом руководстве предназначены для запуска только в браузере, nativeElement в этих тестах всегда является объектом HTMLElement, знакомые методы и свойства которого можно изучить в тесте.

Вот предыдущий тест, переписанный с использованием fixture.debugElement.nativeElement:

it('should find the <p> with fixture.debugElement.nativeElement', () => {
  const bannerDe: DebugElement = fixture.debugElement;
  const bannerEl: HTMLElement = bannerDe.nativeElement;
  const p = bannerEl.querySelector('p')!;
  expect(p.textContent).toEqual('banner works!');
});

У DebugElement есть и другие методы и свойства, полезные в тестах, как вы увидите в других разделах этого руководства.

Символ DebugElement импортируется из библиотеки Angular core.

import {DebugElement} from '@angular/core';

By.css()

Хотя все тесты в этом руководстве запускаются в браузере, некоторые приложения могут по крайней мере часть времени работать на другой платформе.

Например, компонент может сначала отрисовываться на сервере в рамках стратегии ускорения запуска приложения на устройствах с медленным подключением. Средство серверного рендеринга может не поддерживать весь API HTML-элементов. Если оно не поддерживает querySelector, предыдущий тест может завершиться с ошибкой.

DebugElement предоставляет методы поиска, работающие на всех поддерживаемых платформах. Эти методы принимают функцию предикат, которая возвращает true, если узел в дереве DebugElement соответствует критериям выбора.

Создать предикат можно с помощью класса By, импортированного из библиотеки для используемой платформы. Вот импорт By для браузерной платформы:

import {By} from '@angular/platform-browser';

В следующем примере предыдущий тест переписан с использованием DebugElement.query() и метода By.css браузера.

it('should find the <p> with fixture.debugElement.query(By.css)', () => {
  const bannerDe: DebugElement = fixture.debugElement;
  const paragraphDe = bannerDe.query(By.css('p'));
  const p: HTMLElement = paragraphDe.nativeElement;
  expect(p.textContent).toEqual('banner works!');
});

Обратите внимание на следующее:

  • Статический метод By.css() выбирает узлы DebugElement с помощью стандартного селектора CSS.
  • В результате поиска возвращается DebugElement абзаца.
  • Чтобы получить элемент абзаца, необходимо извлечь его из этой обертки.

Если вы фильтруете элементы по селектору CSS и проверяете только свойства нативного элемента браузера, подход с использованием By.css может оказаться избыточным.

Часто проще и понятнее использовать стандартный метод HTMLElement, например querySelector() или querySelectorAll().

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/testing/components-basics

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API