Тестирование автономных компонентов¶
С автономными компонентами Angular становится намного легче: NgModules стали необязательными, а значит, мы можем работать с меньшим количеством косвенных зависимостей. Чтобы сделать это возможным, компоненты теперь напрямую ссылаются на свои зависимости: другие компоненты, а также директивы и пайпы. Существуют так называемые Standalone API для настройки сервисов, таких как HttpClient
.
Дополнительные Standalone API предоставляют имитаторы для автоматизации тестирования. Здесь я собираюсь представить эти API. Для этого я сосредоточусь на встроенных инструментах, поставляемых с Angular.
Если вы не хотите использовать только штатные ресурсы, то в ветке third-party-testing
вы найдете те же примеры, основанные на новом Cypress Component Test Runner и на Testing Library.
Настройка тестов¶
Несмотря на то, что в Standalone Components модули необязательны, TestBed
все равно поставляется с модулем тестирования. Он заботится о настройке теста и предоставляет все компоненты, директивы, пайпы и сервисы для теста:
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 31 32 33 34 35 36 37 |
|
В приведенном примере импортируется тестируемый автономный компонент и предоставляются необходимые сервисы через массив providers
. Именно здесь и вступают в игру упомянутые автономные API. Они предоставляют услуги для HttpClient
, маршрутизатора и NGRX.
Функция provideStore
устанавливает хранилище NGRX, provideState
предоставляет фрагмент функции, необходимый для теста, а provideEffects
регистрирует связанный эффект. Ниже мы заменим эти конструкции на макеты.
Интересен метод provideHttpClientTesting
: он заменяет HttpBackend
, используемый за кулисами HttpClient
, на HttpTestingBackend
, который имитирует HTTP-вызовы. Следует отметить, что его необходимо вызывать после (!) provideHttpClient
.
Поэтому сначала необходимо настроить HttpClient
по умолчанию, чтобы затем переписать отдельные детали для тестирования. Эту схему мы еще увидим ниже при тестировании маршрутизатора.
Макет HttpClient¶
После настройки HttpClient
и HttpTestingBackend
отдельные тесты реализуются как обычно: тест использует HttpTestingController
для получения информации об ожидающих HTTP-запросах и указания симулируемых HTTP-ответов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Затем тест проверяет, обработал ли компонент смоделированный HTTP-ответ так, как было задумано. В показанном случае тест предполагает, что компонент предлагает полученные рейсы через свое свойство flights
.
В конце теста проверяется, что больше нет HTTP-запросов, на которые еще не был получен ответ. Для этого он вызывает метод verify
, предоставляемый HttpTestingController
. Если в этот момент все еще есть открытые запросы, verify
выбрасывает исключение, которое приводит к неудаче теста.
Неглубокое тестирование¶
Если вы тестируете компонент, то автоматически тестируются и все подкомпоненты, директивы и пайпы, используемые в шаблоне. Это нежелательно, особенно для модульных тестов, которые фокусируются на одной единице кода. Кроме того, такое поведение замедляет выполнение тестов при наличии большого количества зависимостей.
Для предотвращения этого используются неглубокие тесты. Это означает, что при настройке тестов все зависимости заменяются имитаторами. Эти имитаторы должны иметь тот же интерфейс, что и заменяемые зависимости. В случае компонентов это означает, помимо прочего, что должны быть предложены те же свойства и события (входы и выходы), а также должны использоваться те же селекторы.
Для замены этих зависимостей в TestBed
предлагается метод overrideComponent
:
1 2 3 4 5 6 |
|
В показанном случае FlightSearchComponent
использует в своем шаблоне другой автономный компонент: FlightCardComponent
. Технически это означает, что FlightCardComponent
появляется в массиве imports
компонента FlightSearchComponent
. Для реализации неглубокого теста эта запись удаляется. В качестве замены добавляется FlightCardMock
. Об этом позаботятся методы remove
и add
.
Таким образом, FlightSearchComponent
используется в тесте без реальных зависимостей. Тем не менее, тест может проверить, ведут ли компоненты себя так, как нужно. Например, в следующем листинге проверяется, создает ли FlightSearchComponent
элемент с именем flight-card
для каждого найденного рейса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Имитация маршрутизатора и магазина¶
Тестовая установка, использованная до сих пор, имитировала только HttpCient
. Однако существуют также автономные API для имитации маршрутизатора и NGRX:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
Как и при тестировании HttpClient
, тест сначала настраивает маршрутизатор обычным способом. Затем он использует provideLocationMocks
для переопределения пары внутренних сервисов, а именно Location
и LocationStrategy
. Эта процедура позволяет смоделировать изменение маршрута в тестовых примерах. Вместо традиционного магазина используется MockStore
, который также поставляется с NGRX. Он позволяет свободно определять все содержимое магазина. Это делается либо вызовом provideMockStore
, либо через его метод setState
. Кроме того, provideMockActions
дает нам возможность заменить наблюдаемую actions$
, на которую часто полагаются эффекты NGRX. Тестовый пример, использующий эту настройку, может выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Этот тест предполагает, что FlightSearchComponent
отображает по одной ссылке на каждый рейс в (макетном) магазине. Он имитирует нажатие на первую ссылку и проверяет, переключится ли приложение на ожидаемый маршрут. Чтобы Angular обработал имитацию щелчка и запустил изменение маршрута, должно быть запущено обнаружение изменений. К сожалению, это не происходит автоматически в тестах. Вместо этого его нужно запускать с помощью метода detectChanges
, когда это необходимо. Задействованные операции являются асинхронными. Поэтому используется fakeAsync
, чтобы мы не обременяли себя этим. Это позволяет синхронно обрабатывать отложенные микрозадачи с помощью flush.
.
Эффекты тестирования¶
В MockStore
не запускаются редукторы или эффекты. Первые являются просто функциями и могут быть протестированы простым способом. Замена action$
— хороший способ протестировать эффекты. Настройка теста в предыдущем разделе уже позаботилась об этом. Теперь тест, основанный на ней, может использовать наблюдаемую action$
для отправки действия, на которое реагирует тестируемый эффект:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
В рассматриваемом случае эффект вызывает HTTP-вызов, на который отвечает HttpTestingController
. Ответ содержит три рейса, для простоты представленные тремя пустыми объектами. Наконец, тест проверяет, предоставил ли эффект эти рейсы через исходящее действие.
Заключение¶
Все больше библиотек предлагают автономные API для подражания зависимостям. Они либо предоставляют имитирующую реализацию, либо, по крайней мере, перезаписывают сервисы в реальной реализации, чтобы повысить тестируемость. Модуль TestingModule
по-прежнему используется для создания тестовых настроек. Однако, в отличие от предыдущего, теперь он импортирует отдельные компоненты, директивы и пайпы, которые будут тестироваться. Их классические аналоги, напротив, объявляются. Кроме того, TestingModule
теперь включает в себя провайдеры, устанавливаемые Standalone API.