Основы тестирования компонентов¶
28.02.2022
Компонент, в отличие от всех других частей приложения Angular, сочетает в себе HTML-шаблон и класс TypeScript. Компонент действительно представляет собой шаблон и класс, работающие вместе.
Чтобы адекватно протестировать компонент, необходимо проверить, что они работают вместе так, как задумано.
Такие тесты требуют создания основного элемента компонента в DOM браузера, как это делает Angular, и исследования взаимодействия класса компонента с DOM, как описано в его шаблоне.
Angular TestBed
облегчает этот вид тестирования, как вы увидите в следующих разделах. Но во многих случаях тестирование класса компонента в одиночку, без участия DOM, может подтвердить большую часть поведения компонента простым и более очевидным способом.
Если вы хотите поэкспериментировать с приложением, которое описано в этом руководстве, запустите его в браузере или скачайте и запустите его локально.
Тестирование класса компонента¶
Тестируйте класс компонента самостоятельно, как вы тестировали бы класс сервиса.
Тестирование класса компонента должно быть очень чистым и простым. Он должен тестировать только один модуль.
С первого взгляда должно быть понятно, что проверяет тест.
Рассмотрим этот LightswitchComponent
, который включает и выключает свет (представленный экранным сообщением), когда пользователь нажимает на кнопку.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Вы можете решить только проверить, что метод clicked()
переключает состояние света on/off и устанавливает соответствующее сообщение.
Этот класс компонента не имеет зависимостей. Чтобы протестировать классы такого типа, выполните те же шаги, что и для службы, не имеющей зависимостей:
- Создайте компонент, используя ключевое слово new.
- Посмотрите на его API.
- Утвердите ожидания для его публичного состояния.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Вот DashboardHeroComponent
из учебника Tour of Heroes.
1 2 3 4 5 6 7 |
|
Он появляется в шаблоне родительского компонента, который связывает hero со свойством @Input
и слушает событие, вызванное через свойство selected @Output
.
Вы можете проверить, что код класса работает без создания DashboardHeroComponent
или его родительского компонента.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Когда компонент имеет зависимости, вы можете захотеть использовать TestBed
для создания компонента и его зависимостей.
Следующий WelcomeComponent
зависит от UserService
, чтобы узнать имя пользователя для приветствия.
1 2 3 4 5 6 7 8 9 10 |
|
Вы можете начать с создания макета UserService
, который отвечает минимальным требованиям этого компонента.
1 2 3 4 |
|
Затем предоставьте и внедрите как компонент так и сервис в конфигурацию TestBed
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Затем запустите класс компонента, не забыв вызвать методы lifecycle hooks, как это делает Angular при запуске приложения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Тестирование DOM компонента¶
Тестирование класса компонента так же просто, как тестирование сервиса.
Но компонент — это нечто большее, чем просто его класс. Компонент взаимодействует с DOM и с другими компонентами.
Тесты только для класса могут рассказать вам о поведении класса.
Они не могут сказать вам, будет ли компонент правильно отображаться, реагировать на пользовательский ввод и жесты или интегрироваться с родительскими и дочерними компонентами.
Ни один из предыдущих class-only тестов не может ответить на ключевые вопросы о том, как компоненты ведут себя на экране.
- Привязан ли
Lightswitch.clicked()
к чему-либо так, чтобы пользователь мог вызвать его? - Отображается ли
Lightswitch.message
? - Может ли пользователь выбрать героя, отображаемого
DashboardHeroComponent
? - Отображается ли имя героя так, как ожидалось (например, в верхнем регистре)?
- Отображается ли приветственное сообщение по шаблону
WelcomeComponent
?
Эти вопросы могут не вызывать беспокойства для предыдущих простых компонентов, показанных на рисунке. Но многие компоненты имеют сложное взаимодействие с элементами DOM, описанными в их шаблонах, заставляя HTML появляться и исчезать при изменении состояния компонента.
Чтобы ответить на эти вопросы, необходимо создать элементы DOM, связанные с компонентами, исследовать DOM, чтобы убедиться, что состояние компонента отображается должным образом в нужное время, и смоделировать взаимодействие пользователя с экраном, чтобы определить, вызывают ли эти взаимодействия поведение компонента, как ожидалось.
Чтобы написать эти виды тестов, вы будете использовать дополнительные возможности TestBed
, а также другие вспомогательные средства тестирования.
Тесты, создаваемые CLI¶
CLI по умолчанию создает начальный файл тестов, когда вы просите его сгенерировать новый компонент.
Например, следующая команда CLI генерирует BannerComponent
в папке app/banner
(с встроенным шаблоном и стилями):
1 |
|
Он также генерирует начальный тестовый файл для компонента, banner-external.component.spec.ts
, который выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Поскольку compileComponents
является асинхронным, он использует утилиту waitForAsync
, импортированную из @angular/core/testing
.
Более подробную информацию см. в разделе waitForAsync.
Уменьшить настройку¶
Только последние три строки этого файла действительно тестируют компонент, и все, что они делают, это утверждают, что Angular может создать компонент.
Остальная часть файла — это код настройки, предвосхищающий более продвинутые тесты, которые могут стать необходимыми, если компонент разовьется во что-то существенное.
Вы узнаете об этих расширенных возможностях тестирования в следующих разделах. Пока же вы можете радикально уменьшить этот файл тестов до более удобного размера:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
В этом примере объект метаданных, переданный в TestBed.configureTestingModule
, просто объявляет BannerComponent
, компонент для тестирования.
1 2 3 |
|
Нет необходимости объявлять или импортировать что-либо еще. Модуль тестирования по умолчанию предварительно сконфигурирован с чем-то вроде BrowserModule
из @angular/platform-browser
.
Позже вы вызовете TestBed.configureTestingModule()
с импортом, провайдерами и другими объявлениями в соответствии с вашими потребностями тестирования. Необязательные методы override
позволяют еще более точно настроить аспекты конфигурации.
createComponent()
¶
После настройки TestBed
, вы вызываете его метод createComponent()
.
1 |
|
TestBed.createComponent()
создает экземпляр BannerComponent
, добавляет соответствующий элемент в DOM test-runner и возвращает ComponentFixture
.
Не переконфигурируйте TestBed
после вызова createComponent
.
Метод createComponent
замораживает текущее определение TestBed
, закрывая его для дальнейшей конфигурации.
Вы не можете больше вызывать никаких методов конфигурации TestBed
, ни configureTestingModule()
, ни get()
, ни одного из методов override...
. Если вы попытаетесь, TestBed
выдаст ошибку.
ComponentFixture
¶
ComponentFixture — это тестовый набор для взаимодействия с созданным компонентом и его соответствующим элементом.
Получите доступ к экземпляру компонента через фикстуру и подтвердите его существование с помощью ожидания Jasmine:
1 2 |
|
beforeEach()
¶
Вы будете добавлять больше тестов по мере развития этого компонента. Вместо того, чтобы дублировать конфигурацию TestBed
для каждого теста, вы рефакторите, чтобы вытащить настройку в Jasmine beforeEach()
и некоторые вспомогательные переменные:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Теперь добавьте тест, который получает элемент компонента из fixture.nativeElement
и ищет ожидаемый текст.
1 2 3 4 5 6 7 |
|
nativeElement
¶
Значение ComponentFixture.nativeElement
имеет тип any
. Позже вы встретите DebugElement.nativeElement
, и оно тоже имеет тип any
.
Angular не может знать во время компиляции, каким типом HTML-элемента является nativeElement
и является ли он вообще HTML-элементом. Приложение может быть запущено на небраузерной платформе, такой как сервер или Web Worker, где элемент может иметь ограниченный API или вообще не существовать.
Тесты в этом руководстве предназначены для запуска в браузере, поэтому значение nativeElement
всегда будет HTMLElement
или одним из его производных классов.
Зная, что это какой-то HTMLElement
, используйте стандартный HTML querySelector
, чтобы углубиться в дерево элементов.
Вот еще один тест, который вызывает HTMLElement.querySelector
для получения элемента абзаца и поиска текста баннера:
1 2 3 4 5 6 |
|
DebugElement
¶
Angular fixture предоставляет элемент компонента напрямую через fixture.nativeElement
.
1 |
|
На самом деле это удобный метод, реализованный как fixture.debugElement.nativeElement
.
1 2 |
|
Для такого извилистого пути к элементу есть веская причина.
Свойства nativeElement
зависят от среды выполнения. Вы можете выполнять эти тесты на небраузерной платформе, которая не имеет DOM или чья DOM-эмуляция не поддерживает полный API HTMLElement
.
Angular полагается на абстракцию DebugElement
для безопасной работы на всех поддерживаемых платформах. Вместо создания дерева элементов HTML, Angular создает дерево DebugElement
, которое оборачивает нативные элементы для платформы выполнения.
Свойство nativeElement
разворачивает DebugElement
и возвращает объект элемента для конкретной платформы.
Поскольку примеры тестов для этого руководства предназначены для запуска только в браузере, nativeElement
в этих тестах всегда является HTMLElement
, чьи знакомые методы и свойства вы можете исследовать в тесте.
Вот предыдущий тест, повторно реализованный с fixture.debugElement.nativeElement
:
1 2 3 4 5 6 |
|
У DebugElement
есть другие методы и свойства, которые полезны в тестах, как вы увидите в других частях этого руководства.
Вы импортируете символ DebugElement
из библиотеки ядра Angular.
1 |
|
By.css()
¶
Хотя все тесты в этом руководстве выполняются в браузере, некоторые приложения могут работать на другой платформе хотя бы часть времени.
Например, компонент может сначала отрисовываться на сервере в рамках стратегии, направленной на ускорение запуска приложения на устройствах с плохим соединением. Рендерер на стороне сервера может не поддерживать полный API элементов HTML.
Если он не поддерживает querySelector
, предыдущий тест может оказаться неудачным.
Элемент DebugElement
предлагает методы запроса, которые работают для всех поддерживаемых платформ. Эти методы запроса принимают функцию predicate, которая возвращает true
, если узел в дереве DebugElement
соответствует критериям выбора.
Вы создаете предикат с помощью класса By
, импортированного из библиотеки для платформы выполнения. Вот импорт By
для платформы браузера:
1 |
|
Следующий пример повторно реализует предыдущий тест с помощью DebugElement.query()
и метода браузера By.css
.
1 2 3 4 5 6 |
|
Некоторые примечательные наблюдения:
- Статический метод
By.css()
выбирает узлыDebugElement
со стандартным CSS-селектором. - Запрос возвращает
DebugElement
для параграфа. - Вы должны развернуть этот результат, чтобы получить элемент абзаца.
Когда вы фильтруете по селектору CSS и проверяете только свойства родного элемента браузера, подход By.css
может оказаться излишним.
Часто проще и понятнее фильтровать с помощью стандартного метода HTMLElement
, такого как querySelector()
или querySelectorAll()
.