Сценарии тестирования компонентов¶
28.02.2022
В этом руководстве рассматриваются общие сценарии использования тестирования компонентов.
Если вы хотите поэкспериментировать с приложением, которое описано в этом руководстве, запустите его в браузере или скачайте и запустите его локально.
Привязка компонентов¶
В примере приложения BannerComponent
представляет статический текст заголовка в шаблоне HTML.
После нескольких изменений BannerComponent
представляет динамический заголовок, привязываясь к свойству title
компонента следующим образом.
1 2 3 4 5 6 7 8 |
|
Как бы минимально это ни было, вы решили добавить тест, чтобы подтвердить, что компонент действительно отображает нужное содержимое там, где вы думаете.
Запрос для <h1>
¶
Вы напишете последовательность тестов, которые проверяют значение элемента <h1>
, который обертывает привязку интерполяции свойства title.
Вы обновите beforeEach
, чтобы найти этот элемент с помощью стандартного HTML querySelector
и присвоить его переменной h1
.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
createComponent()
не связывает данные¶
Для первого теста вы хотите убедиться, что на экране отображается стандартный title
. Ваш инстинкт подсказывает вам написать тест, который сразу же проверяет <h1>
следующим образом:
1 2 3 |
|
Этот тест проваливается с сообщением:
1 |
|
Привязка происходит, когда Angular выполняет обнаружение изменений.
В продакшене обнаружение изменений срабатывает автоматически, когда Angular создает компонент, пользователь нажимает клавишу или завершается асинхронная активность (например, AJAX).
Функция TestBed.createComponent
не запускает обнаружение изменений; этот факт подтверждается в пересмотренном тесте:
1 2 3 |
|
detectChanges()
¶
Вы должны указать TestBed
на выполнение привязки данных, вызвав fixture.detectChanges()
. Только тогда <h1>
будет иметь ожидаемый заголовок.
1 2 3 4 |
|
Отложенное обнаружение изменений является намеренным и полезным. Она дает тестеру возможность проверить и изменить состояние компонента до того, как Angular инициирует привязку данных и вызовет lifecycle hooks.
Вот еще один тест, который изменяет свойство title
компонента до вызова fixture.detectChanges()
.
1 2 3 4 5 |
|
Автоматическое обнаружение изменений¶
Тесты BannerComponent
часто вызывают detectChanges
. Некоторые тестировщики предпочитают, чтобы тестовая среда Angular выполняла обнаружение изменений автоматически.
Это возможно, если настроить TestBed
с провайдером ComponentFixtureAutoDetect
. Сначала импортируйте его из библиотеки утилит для тестирования:
1 |
|
Затем добавьте его в массив providers
конфигурации модуля тестирования:
1 2 3 4 5 6 7 8 9 |
|
Вот три теста, которые иллюстрируют работу автоматического обнаружения изменений.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Первый тест показывает преимущество автоматического обнаружения изменений.
Второй и третий тесты показывают важное ограничение. Среда тестирования Angular не знает, что тест изменил заголовок
компонента.
Служба ComponentFixtureAutoDetect
реагирует на асинхронные действия, такие как разрешение обещаний, таймеры и события DOM.
Но прямое, синхронное обновление свойства компонента невидимо.
Тест должен вызвать fixture.detectChanges()
вручную, чтобы запустить очередной цикл обнаружения изменений.
Вместо того, чтобы гадать, когда тестовое приспособление будет или не будет выполнять обнаружение изменений, примеры в этом руководстве всегда вызывают detectChanges()
явно. Нет никакого вреда в том, чтобы вызывать detectChanges()
чаще, чем это необходимо.
Изменение входного значения с помощью dispatchEvent()
¶
Чтобы имитировать ввод данных пользователем, найдите элемент input и установите его свойство value
.
Вы вызовете fixture.detectChanges()
, чтобы запустить обнаружение изменений Angular. Но есть важный, промежуточный шаг.
Angular не знает, что вы установили свойство value
элемента ввода. Он не будет читать это свойство, пока вы не поднимете событие input
элемента, вызвав dispatchEvent()
.
Тогда вы вызываете detectChanges()
.
Следующий пример демонстрирует правильную последовательность действий.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Компонент с внешними файлами¶
Предыдущий BannerComponent
определен с inline template и inline css, указанными в свойствах @Component.template
и @Component.styles
соответственно.
Многие компоненты определяют внешние шаблоны и внешние css с помощью свойств @Component.templateUrl
и @Component.styleUrls
соответственно, как это делает следующий вариант BannerComponent
.
1 2 3 4 5 |
|
Этот синтаксис указывает компилятору Angular читать внешние файлы во время компиляции компонента.
Это не проблема, когда вы выполняете команду CLI ng test
, потому что она компилирует приложение перед запуском тестов.
Однако, если вы запускаете тесты в не-CLI окружении, тесты этого компонента могут не пройти. Например, если вы запустите тесты BannerComponent
в среде веб-кодирования, такой как plunker, вы увидите сообщение, подобное этому:
1 2 3 |
|
Вы получаете это сообщение о сбое теста, когда среда выполнения компилирует исходный код во время выполнения самих тестов.
Чтобы устранить проблему, вызовите compileComponents()
, как объясняется в следующем разделе Вызов compileComponents.
Компонент с зависимостью¶
Компоненты часто имеют зависимости от сервисов.
Компонент WelcomeComponent
отображает приветственное сообщение для вошедшего в систему пользователя. Он знает, кто этот пользователь, на основе свойства инжектированного UserService
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
В WelcomeComponent
есть логика принятия решений, которая взаимодействует с сервисом, логика, которая делает этот компонент достойным тестирования. Вот конфигурация модуля тестирования для файла спецификации:
1 2 3 4 5 6 7 8 |
|
На этот раз, в дополнение к объявлению component-under-test, конфигурация добавляет провайдера UserService
в список providers
.
Но не настоящий UserService
.
Предоставление дублей для тестирования сервисов¶
В компонент под тестом не обязательно должны быть внедрены реальные сервисы. На самом деле, обычно лучше, если это будут тестовые дубликаты, такие как заглушки, подделки, шпионы или моки.
Целью спецификации является тестирование компонента, а не сервиса, а с реальными сервисами могут возникнуть проблемы.
Внедрение настоящего UserService
может стать кошмаром. Настоящая служба может запросить у пользователя учетные данные для входа в систему и попытаться связаться с сервером аутентификации.
Такое поведение может быть трудно перехватить.
Гораздо проще и безопаснее создать и зарегистрировать тестового двойника вместо настоящего UserService
.
Этот конкретный набор тестов предоставляет минимальный макет UserService
, который удовлетворяет потребности WelcomeComponent
и его тестов:
1 2 3 4 5 6 |
|
Получение инжектированных сервисов¶
Тестам нужен доступ к заглушке UserService
, инжектированной в WelcomeComponent
.
Angular имеет иерархическую систему инъекций. Инжекторы могут быть на нескольких уровнях, начиная с корневого инжектора, созданного TestBed
, и заканчивая деревом компонентов.
Самый безопасный способ получить инжектируемый сервис, способ, который всегда работает, это получить его из инжектора компонента, который тестируется.
Инжектор компонента является свойством DebugElement
приспособления.
1 2 3 4 |
|
TestBed.inject()
¶
Вы можете также получить сервис из корневого инжектора, используя TestBed.inject()
. Это проще для запоминания и менее многословно.
Но он работает только тогда, когда Angular инжектирует компонент с экземпляром сервиса в корневой инжектор теста.
В этом тестовом наборе единственным поставщиком UserService
является корневой модуль тестирования, поэтому безопасно вызывать TestBed.inject()
следующим образом:
1 2 |
|
Случай использования, когда TestBed.inject()
не работает, смотрите в разделе Override component providers, где объясняется, когда и почему вы должны получить сервис из инжектора компонента.
Окончательная настройка и тесты¶
Вот полный beforeEach()
, использующий TestBed.inject()
:
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 |
|
И вот несколько тестов:
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 |
|
Первый тест — это тест на вменяемость; он подтверждает, что заглушка UserService
вызвана и работает.
Второй параметр (например, 'expected name'
) является необязательной меткой отказа. Если ожидание не сработало, Jasmine добавляет эту метку к сообщению о неудаче ожидания.
В спецификации с несколькими ожиданиями это может помочь прояснить, что пошло не так и какое ожидание не сработало.
Остальные тесты подтверждают логику работы компонента, когда сервис возвращает различные значения. Второй тест проверяет эффект от изменения имени пользователя. Третий тест проверяет, что компонент выводит правильное сообщение, когда нет зарегистрированного пользователя.
Компонент с асинхронным сервисом¶
В этом примере шаблон AboutComponent
содержит TwainComponent
. Компонент TwainComponent
отображает цитаты Марка Твена.
1 2 3 4 |
|
Значение свойства quote
компонента проходит через AsyncPipe
. Это означает, что свойство возвращает либо Promise
, либо Observable
.
В этом примере метод TwainComponent.getQuote()
сообщает, что свойство quote
возвращает Observable
.
1 2 3 4 5 6 7 8 9 10 11 |
|
Компонент TwainComponent
получает цитаты от инжектированного TwainService
. Компонент запускает возвращаемую Observable
со значением заполнителя ('...'
), прежде чем сервис сможет вернуть свою первую цитату.
Компонент catchError
перехватывает ошибки сервиса, подготавливает сообщение об ошибке и возвращает значение placeholder на канале успеха. Он должен подождать тик для установки errorMessage
, чтобы избежать двойного обновления сообщения в одном и том же цикле обнаружения изменений.
Все эти функции вы захотите протестировать.
Тестирование с помощью spy¶
При тестировании компонента значение имеет только публичный API сервиса. В целом, сами тесты не должны выполнять вызовы на удаленные серверы.
Они должны эмулировать такие вызовы.
Настройка в этом app/twain/twain.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 |
|
Сосредоточьтесь на шпионе.
1 2 3 4 5 6 7 8 |
|
Шпион устроен таким образом, что при любом вызове getQuote
получает наблюдаемую с тестовой цитатой. В отличие от реального метода getQuote()
, этот шпион обходит сервер и возвращает синхронную наблюдаемую, значение которой доступно немедленно.
Вы можете написать много полезных тестов с помощью этого шпиона, несмотря на то, что его Observable
является синхронным.
Синхронные тесты¶
Ключевым преимуществом синхронного Observable
является то, что вы можете часто превращать асинхронные процессы в синхронные тесты.
1 2 3 4 5 6 7 8 9 |
|
Поскольку результат шпионажа возвращается синхронно, метод getQuote()
обновляет сообщение на экране сразу после первого цикла обнаружения изменений, во время которого Angular вызывает ngOnInit
.
При тестировании пути ошибок вам не так повезет. Хотя сервисный шпион возвращает ошибку синхронно, метод компонента вызывает setTimeout()
.
Тест должен подождать как минимум один полный оборот движка JavaScript, прежде чем значение станет доступным.
Тест должен стать асинхронным.
Асинхронный тест с fakeAsync()
¶
Чтобы использовать функциональность fakeAsync()
, вы должны импортировать zone.js/testing
в файл настройки тестов. Если вы создали свой проект с помощью Angular CLI, то zone-testing
уже импортирован в src/test.ts
.
Следующий тест подтверждает ожидаемое поведение, когда сервис возвращает ErrorObservable
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Функция it()
принимает аргумент следующего вида.
1 2 3 |
|
Функция fakeAsync()
позволяет использовать линейный стиль кодирования, запуская тело теста в специальной тестовой зоне fakeAsync
. Тело теста кажется синхронным. В нем нет вложенного синтаксиса (например, Promise.then()
), который нарушает поток управления.
Ограничение: Функция fakeAsync()
не будет работать, если тело теста выполняет вызов XMLHttpRequest
(XHR). Вызовы XHR внутри теста встречаются редко, но если вам необходимо вызвать XHR, смотрите раздел waitForAsync()
.
Функция tick()
¶
Вы должны вызывать tick(), чтобы перевести виртуальные часы.
Вызов tick() имитирует течение времени до завершения всех ожидающих асинхронных действий. В данном случае он ожидает setTimeout()
обработчика ошибок.
Функция tick() принимает в качестве параметров millis
и tickOptions
. Параметр millis
определяет, на сколько продвигаются виртуальные часы, и по умолчанию равен 0
, если не указан. Например, если у вас есть setTimeout(fn, 100)
в тесте fakeAsync()
, вам нужно использовать tick(100)
для запуска обратного вызова fn.
Необязательный параметр tickOptions
имеет свойство processNewMacroTasksSynchronously
. Свойство processNewMacroTasksSynchronously
указывает, следует ли вызывать новые сгенерированные макрозадачи при тике, и по умолчанию имеет значение true
.
1 2 3 4 5 6 7 8 |
|
Функция tick() является одной из утилит тестирования Angular, которую вы импортируете с помощью TestBed
. Она является компаньоном fakeAsync()
и вы можете вызывать ее только в теле fakeAsync()
.
tickOptions¶
В этом примере появилась новая макрозадача — вложенная функция setTimeout
. По умолчанию, когда tick
имеет значение setTimeout, срабатывают и outside
, и nested
.
1 2 3 4 5 6 7 8 9 10 11 |
|
В некоторых случаях вы не хотите запускать новую макрозадачу при тике. Вы можете использовать tick(millis, {processNewMacroTasksSynchronously: false})
, чтобы не вызывать новую макрозадачу.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Сравнение дат внутри fakeAsync()¶
fakeAsync()
имитирует течение времени, что позволяет вам вычислить разницу между датами внутри fakeAsync()
.
1 2 3 4 5 6 |
|
jasmine.clock с fakeAsync()¶
Jasmine также предоставляет функцию clock
для имитации дат. Angular автоматически запускает тесты, которые выполняются после вызова jasmine.clock().install()
внутри метода fakeAsync()
до вызова jasmine.clock().uninstall()
.
Метод fakeAsync()
не нужен и при вложении выдает ошибку.
По умолчанию эта функция отключена. Чтобы включить ее, установите глобальный флаг перед импортом zone-testing
.
Если вы используете Angular CLI, настройте этот флаг в src/test.ts
.
1 2 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Использование планировщика RxJS внутри fakeAsync()¶
Вы также можете использовать планировщик RxJS в fakeAsync()
точно так же, как и setTimeout()
или setInterval()
, но вам нужно импортировать zone.js/plugins/zone-patch-rxjs-fake-async
для патча планировщика RxJS.
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 |
|
Поддержка большего количества макрозадач¶
По умолчанию fakeAsync()
поддерживает следующие макрозадачи.
setTimeout
setInterval
requestAnimationFrame
webkitRequestAnimationFrame
mozRequestAnimationFrame
Если вы запускаете другие макрозадачи, такие как HTMLCanvasElement.toBlob()
, возникает ошибка "Unknown macroTask scheduled in fake async test".
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 |
|
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 |
|
Если вы хотите поддерживать такой случай, вам необходимо определить макрозадачу, которую вы хотите поддерживать, в beforeEach()
. Например:
1 2 3 4 5 6 7 8 |
|
Чтобы сделать элемент <canvas>
Zone.js-aware в вашем приложении, вам необходимо импортировать патч zone-patch-canvas
(либо в polyfills.ts
, либо в конкретный файл, который использует <canvas>
):
1 2 3 4 |
|
Асинхронные наблюдаемые¶
Вы можете быть довольны покрытием этих тестов.
Однако вас может беспокоить тот факт, что реальный сервис ведет себя не совсем так. Реальный сервис посылает запросы на удаленный сервер.
Серверу требуется время для ответа, и ответ, конечно, не будет доступен сразу, как в предыдущих двух тестах.
Ваши тесты будут более точно отражать реальный мир, если вы вернете асинхронную наблюдаемую из шпиона getQuote()
следующим образом.
1 2 |
|
Помощники асинхронных наблюдаемых¶
Асинхронная наблюдаемая была создана с помощью помощника asyncData
. Помощник asyncData
— это служебная функция, которую вам придется написать самостоятельно, или скопировать эту функцию из кода примера.
1 2 3 4 5 6 7 |
|
Эта вспомогательная наблюдаемая испускает значение data
при следующем повороте движка JavaScript.
Оператор RxJS defer()
возвращает наблюдаемую. Он принимает фабричную функцию, которая возвращает либо обещание, либо наблюдаемую.
Когда что-то подписывается на наблюдаемую defer, оно добавляет подписчика в новую наблюдаемую, созданную с помощью этой фабрики.
Оператор defer()
преобразует Promise.resolve()
в новую наблюдаемую, которая, как и HttpClient
, испускается один раз и завершается. Подписчики отписываются после получения значения данных.
Существует аналогичный помощник для создания асинхронной ошибки.
1 2 3 4 5 6 7 |
|
Больше асинхронных тестов¶
Теперь, когда шпион getQuote()
возвращает асинхронные наблюдаемые, большинство ваших тестов также должны быть асинхронными.
Вот тест fakeAsync()
, который демонстрирует поток данных, ожидаемый в реальном мире.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Заметьте, что элемент quote отображает значение заполнителя (...
) после ngOnInit()
. Первая цитата еще не пришла.
Чтобы удалить первую котировку из наблюдаемой, вы вызываете tick(). Затем вызовите detectChanges()
, чтобы сообщить Angular об обновлении экрана.
После этого можно утверждать, что элемент цитаты отображает ожидаемый текст.
Асинхронный тест с waitForAsync()
¶
Чтобы использовать функциональность waitForAsync()
, вы должны импортировать zone.js/testing
в файл настройки тестов. Если вы создали свой проект с помощью Angular CLI, zone-testing
уже импортирован в src/test.ts
.
Вот предыдущий тест fakeAsync()
, переписанный с помощью утилиты waitForAsync()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Утилита waitForAsync()
скрывает некоторые асинхронные шаблоны, организуя выполнение кода тестировщика в специальной async test zone. Вам не нужно передавать done()
из Jasmine в тест и вызывать done()
, потому что он неопределен
в обещаниях или наблюдаемых обратных вызовах.
Но асинхронная природа теста проявляется в вызове fixture.whenStable()
, который нарушает линейный поток управления.
При использовании intervalTimer()
, например, setInterval()
в waitForAsync()
, не забудьте отменить таймер с помощью clearInterval()
после выполнения теста, иначе waitForAsync()
никогда не завершится.
whenStable
¶
Тест должен ждать, пока наблюдаемая getQuote()
выпустит следующую цитату. Вместо вызова tick(), он вызывает fixture.whenStable()
.
Функция fixture.whenStable()
возвращает обещание, которое разрешается, когда очередь задач движка JavaScript становится пустой. В этом примере очередь задач становится пустой, когда наблюдаемая испускает первую цитату.
Тест возобновляется в обратном вызове обещания, которое вызывает detectChanges()
для обновления элемента цитаты ожидаемым текстом.
Jasmine done()
¶
Хотя функции waitForAsync()
и fakeAsync()
значительно упрощают асинхронное тестирование Angular, вы все еще можете вернуться к традиционной технике и передать it
функцию, которая принимает done
callback.
Вы не можете вызвать done()
в функциях waitForAsync()
или fakeAsync()
, потому что параметр done
является неопределенным
.
Теперь вы отвечаете за цепочку обещаний, обработку ошибок и вызов done()
в нужные моменты.
Написание тестовых функций с done()
более громоздко, чем waitForAsync()
и fakeAsync()
, но иногда это необходимо, когда код включает intervalTimer()
типа setInterval
.
Вот еще две версии предыдущего теста, написанные с помощью done()
. Первая подписывается на Observable
, выставляемый шаблону свойством quote
компонента.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Оператор RxJS last()
выдает последнее значение наблюдаемой перед завершением, которое будет тестовой цитатой. Обратный вызов subscribe
вызывает detectChanges()
для обновления элемента quote тестовой цитатой, аналогично предыдущим тестам.
В некоторых тестах вас больше интересует, как был вызван метод инжектированного сервиса и какие значения он вернул, чем то, что отображается на экране.
Сервисный шпион, такой как qetQuote()
шпион поддельного TwainService
, может дать вам эту информацию и сделать утверждения о состоянии представления.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Компонентные мраморные тесты¶
Предыдущие тесты TwainComponent
имитировали асинхронный наблюдаемый ответ от TwainService
с помощью утилит asyncData
и asyncError
.
Это короткие, простые функции, которые вы можете написать самостоятельно. К сожалению, они слишком просты для многих распространенных сценариев.
Наблюдаемая часто испускается несколько раз, возможно, со значительной задержкой.
Компонент может координировать несколько наблюдаемых с перекрывающимися последовательностями значений и ошибок.
RxJS marble testing — это отличный способ тестирования сценариев наблюдаемых, как простых, так и сложных. Вы, вероятно, видели мраморные диаграммы, которые иллюстрируют работу наблюдаемых.
Мраморное тестирование использует аналогичный мраморный язык для определения наблюдаемых потоков и ожиданий в ваших тестах.
Следующие примеры рассматривают два теста TwainComponent
с помощью мраморного тестирования.
Начните с установки npm-пакета jasmine-marbles
. Затем импортируйте необходимые вам символы.
1 |
|
Вот полный тест для получения цитаты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Обратите внимание, что тест Jasmine является синхронным. Здесь нет fakeAsync()
.
Мраморное тестирование использует планировщик тестов для имитации течения времени в синхронном тесте.
Красота мраморного тестирования заключается в визуальном определении наблюдаемых потоков. Этот тест определяет холодную наблюдаемую, которая ждет три кадра (---
), выдает значение (x
) и завершается (|
).
Во втором аргументе вы сопоставляете маркер значения (x
) с испускаемым значением (testQuote
).
1 |
|
Библиотека marble создает соответствующую наблюдаемую, которую тест устанавливает в качестве возвращаемого значения шпиона getQuote
.
Когда вы будете готовы активировать мраморные наблюдаемые, вы скажете TestScheduler
промыть свою очередь подготовленных задач следующим образом.
1 |
|
Этот шаг служит цели, аналогичной tick() и whenStable()
в предыдущих примерах fakeAsync()
и waitForAsync()
. Остальная часть теста такая же, как и в этих примерах.
Тестирование ошибок в мраморе¶
Перед вами мраморная версия теста на ошибку getQuote()
.
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 |
|
Это все еще асинхронный тест, вызывающий fakeAsync()
и tick(), потому что сам компонент вызывает setTimeout()
при обработке ошибок.
Посмотрите на определение мраморной наблюдаемой.
1 2 3 4 5 |
|
Это холодная наблюдаемая, которая ждет три кадра и затем выдает ошибку, хэш (#
) символ указывает на время ошибки, которое указано в третьем аргументе. Второй аргумент равен null, потому что наблюдаемая никогда не выдает значения.
Узнайте о тестировании на мраморе¶
Мраморный кадр — это виртуальная единица времени тестирования. Каждый символ (-
, x
, |
, #
) отмечает прохождение одного фрейма.
Холодная наблюдаемая не производит значений, пока вы не подпишетесь на нее. Большинство наблюдаемых вашего приложения являются холодными.
Все методы HttpClient возвращают холодные наблюдаемые.
Горячая наблюдаемая уже производит значения до того, как вы на нее подписались. Наблюдаемая Router.events
, которая сообщает об активности маршрутизатора, является горячей наблюдаемой.
Тестирование мрамора RxJS — это обширная тема, выходящая за рамки данного руководства. Узнайте об этом в Интернете, начиная с официальной документации.
Компонент с входами и выходами¶
Компонент с входами и выходами обычно появляется внутри шаблона представления главного компонента. Хост использует привязку свойств для установки входного свойства и привязку событий для прослушивания событий, вызванных выходным свойством.
Цель тестирования — проверить, что такие привязки работают так, как ожидается. Тесты должны устанавливать входные значения и прослушивать выходные события.
Компонент DashboardHeroComponent
— это маленький пример компонента в этой роли. Он отображает отдельного героя, предоставленного DashboardComponent
.
Щелчок на этом герое сообщает DashboardComponent
, что пользователь выбрал героя.
Компонент DashboardHeroComponent
встраивается в шаблон DashboardComponent
следующим образом:
1 2 3 4 5 6 7 |
|
Компонент DashboardHeroComponent
появляется в повторителе *ngFor
, который устанавливает входное свойство hero
каждого компонента в значение цикла и слушает событие selected
компонента.
Вот полное определение компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Хотя тестирование такого простого компонента имеет небольшую внутреннюю ценность, стоит знать, как это сделать. Используйте один из этих подходов:
- Протестируйте его как используемый
DashboardComponent
. - Тестируйте его как отдельный компонент
- Протестируйте его в качестве замены
DashboardComponent
.
Беглый взгляд на конструктор DashboardComponent
отталкивает от первого подхода:
1 |
|
Компонент DashboardComponent
зависит от Angular router и HeroService
. Скорее всего, вам придется заменить их оба тестовыми дублями, а это большая работа.
Маршрутизатор кажется особенно сложным.
В следующем обсуждении рассматривается тестирование компонентов, для которых требуется маршрутизатор.
Ближайшей целью является тестирование DashboardHeroComponent
, а не DashboardComponent
, поэтому попробуйте второй и третий варианты.
Тест DashboardHeroComponent
stand-alone¶
Вот основная часть настройки файла спецификации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Обратите внимание, как код установки назначает тестового героя (expectedHero
) свойству компонента hero
, эмулируя способ, которым DashboardComponent
установит его с помощью привязки свойства в своем ретрансляторе.
Следующий тест проверяет, что имя героя передается в шаблон с помощью привязки.
1 2 3 4 |
|
Поскольку template передает имя героя через Angular UpperCasePipe
, тест должен сопоставить значение элемента с именем в верхнем регистре.
Этот небольшой тест демонстрирует, как тесты Angular могут проверить визуальное представление компонента — то, что невозможно с помощью тестов классов компонентов — с небольшими затратами и без использования гораздо более медленных и сложных сквозных тестов.
Щелчок¶
Нажатие на героя должно вызвать событие selected
, которое может услышать главный компонент (DashboardComponent
предположительно):
1 2 3 4 5 6 7 8 9 |
|
Свойство компонента selected
возвращает EventEmitter
, который для потребителей выглядит как RxJS синхронный Observable
. Тест подписывается на него явно, так же как и компонент-хозяин неявно.
Если компонент ведет себя так, как ожидается, щелчок по элементу героя должен заставить свойство selected
компонента выдать объект hero
.
Тест обнаруживает это событие через свою подписку на selected
.
triggerEventHandler
¶
heroDe
в предыдущем тесте — это DebugElement
, который представляет героя <div>
.
Он имеет свойства и методы Angular, которые абстрагируют взаимодействие с родным элементом. Этот тест вызывает DebugElement.triggerEventHandler
с именем события "click".
Привязка события "click" отвечает вызовом DashboardHeroComponent.click()
.
Angular DebugElement.triggerEventHandler
может вызывать любое связанное с данными событие по своему имени события. Вторым параметром является объект события, переданный обработчику.
Тест вызвал событие "клик".
1 |
|
В этом случае тест правильно предполагает, что обработчик события во время выполнения, метод click()
компонента, не заботится об объекте события.
Другие обработчики менее снисходительны. Например, директива RouterLink
ожидает объект со свойством button
, которое определяет, какая кнопка мыши, если таковая имеется, была нажата во время щелчка.
Директива RouterLink
выбрасывает ошибку, если объект события отсутствует.
Клик по элементу¶
Следующая тестовая альтернатива вызывает собственный метод click()
родного элемента, что вполне подходит для этого компонента.
1 2 3 4 5 6 7 8 9 |
|
click()
helper¶
Нажатие на кнопку, якорь или произвольный элемент HTML является распространенной задачей тестирования.
Чтобы сделать ее последовательной и простой, инкапсулируйте процесс нажатия в помощнике, таком как следующая функция click()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Первый параметр — это элемент для клика. Если хотите, передайте пользовательский объект события в качестве второго параметра.
По умолчанию используется частичный объект события левой кнопки мыши, принимаемый многими обработчиками, включая директиву RouterLink
.
Вспомогательная функция click()
не является одной из утилит тестирования Angular. Это функция, определенная в коде примера этого руководства. Все примеры тестов используют ее.
Если она вам нравится, добавьте ее в свою собственную коллекцию помощников.
Вот предыдущий тест, переписанный с использованием помощника click
.
1 2 3 4 5 6 7 8 9 10 |
|
Компонент внутри тестового хоста¶
В предыдущих тестах роль хоста DashboardComponent
выполняли сами компоненты. Но правильно ли работает DashboardHeroComponent
при правильной привязке данных к компоненту хоста?
Вы могли бы тестировать с помощью фактического DashboardComponent
. Но это может потребовать много настроек, особенно если в его шаблоне есть повторитель *ngFor
, другие компоненты, HTML макета, дополнительные привязки, конструктор, который внедряет множество сервисов, и он сразу же начинает взаимодействовать с этими сервисами.
Представьте себе, сколько усилий нужно приложить, чтобы отключить все эти отвлекающие факторы, только чтобы доказать, что можно удовлетворительно доказать с помощью тестового хоста, подобного этому:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Этот тестовый хост привязывается к DashboardHeroComponent
, как и DashboardComponent
, но без шума Router
, HeroService
или повторителя *ngFor
.
Тестовый хост устанавливает свойство ввода компонента hero
со своим тестовым героем. Он связывает событие selected
компонента с его обработчиком onSelected
, который записывает выданного героя в свойство selectedHero
.
Позже тесты смогут проверить selectedHero
, чтобы убедиться, что событие DashboardHeroComponent.selected
вызвало ожидаемого героя.
Настройка для тестов test-host
аналогична настройке для автономных тестов:
1 2 3 4 5 6 7 8 9 10 11 |
|
Эта конфигурация модуля тестирования показывает три важных отличия:
- Он объявляет и
DashboardHeroComponent
, иTestHostComponent
. - Он создает
TestHostComponent
вместоDashboardHeroComponent
. - Компонент
TestHostComponent
устанавливаетDashboardHeroComponent.hero
с привязкой
Функция createComponent
возвращает fixture
, содержащий экземпляр TestHostComponent
вместо экземпляра DashboardHeroComponent
.
Создание TestHostComponent
имеет побочный эффект создания DashboardHeroComponent
, потому что последний появляется в шаблоне первого. Запрос на элемент героя (heroEl
) по-прежнему находит его в тестовом DOM, хотя и на большей глубине дерева элементов, чем раньше.
Сами тесты практически идентичны автономной версии:
1 2 3 4 5 6 7 8 9 10 |
|
Отличается только тест выбранного события. Он подтверждает, что выбранный герой DashboardHeroComponent
действительно находит путь наверх через привязку событий к главному компоненту.
Компонент маршрутизации¶
Компонент маршрутизации — это компонент, который указывает Router
на переход к другому компоненту. Компонент DashboardComponent
является компонентом маршрутизации, потому что пользователь может перейти к компоненту HeroDetailComponent
, нажав на одну из геройских кнопок на приборной панели.
Маршрутизация довольно сложна. Тестирование DashboardComponent
казалось сложным отчасти потому, что в нем задействован Router
, который он внедряет вместе с HeroService
.
1 |
|
1 2 3 4 |
|
Angular предоставляет вспомогательные средства тестирования для уменьшения количества шаблонов и более эффективного тестирования кода, который зависит от Router
и HttpClient
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Следующий тест нажимает на отображаемого героя и подтверждает, что мы переходим на ожидаемый URL.
1 2 3 4 5 6 7 8 9 10 11 |
|
Маршрутизируемые компоненты¶
Маршрутизируемый компонент является конечным пунктом навигации Router
. Его тестирование может быть более сложным, особенно если маршрут к компоненту включает параметры.
Компонент HeroDetailComponent
— это маршрутизируемый компонент, который является конечным пунктом такого маршрута.
Когда пользователь нажимает на героя Dashboard, DashboardComponent
говорит Router
перейти к heroes/:id
. :id
— это параметр маршрута, значением которого является id
героя для редактирования.
Маршрутизатор Router
сопоставляет этот URL с маршрутом к компоненту HeroDetailComponent
. Он создает объект ActivatedRoute
с информацией о маршрутизации и вставляет его в новый экземпляр HeroDetailComponent
.
Вот конструктор HeroDetailComponent
:
1 2 3 4 5 |
|
Компоненту HeroDetail
необходим параметр id
, чтобы он мог получить соответствующего героя с помощью HeroDetailService
. Компонент должен получить id
из свойства ActivatedRoute.paramMap
, которое является Observable
.
Он не может просто ссылаться на свойство id
из ActivatedRoute.paramMap
. Компонент должен подписаться на наблюдаемую ActivatedRoute.paramMap
и быть готовым к тому, что id
может измениться в течение его жизни.
1 2 3 4 |
|
В разделе ActivatedRoute в действии руководства Router tutorial: tour of heroes более подробно рассматривается ActivatedRoute.paramMap
.
Тесты могут исследовать, как HeroDetailComponent
реагирует на различные значения параметра id
, переходя к различным маршрутам.
Тестирование с помощью RouterTestingHarness
¶
Вот тест, демонстрирующий поведение компонента, когда наблюдаемый id
относится к существующему герою:
1 2 3 4 5 6 7 8 9 10 |
|
В следующем разделе рассматриваются метод createComponent()
и объект page
. Пока полагайтесь на свою интуицию.
Когда id
не может быть найден, компонент должен перенаправить на HeroListComponent
.
При настройке тестового набора был использован тот же жгут маршрутизаторов описанный выше.
Этот тест ожидает, что компонент попытается перейти к HeroListComponent
.
1 2 3 4 5 6 7 8 9 10 11 |
|
Тесты вложенных компонентов¶
Шаблоны компонентов часто имеют вложенные компоненты, шаблоны которых могут содержать еще больше компонентов.
Дерево компонентов может быть очень глубоким, и в большинстве случаев вложенные компоненты не играют никакой роли в тестировании компонента, находящегося в верхней части дерева.
Например, AppComponent
отображает навигационную панель с якорями и их директивами RouterLink
.
1 2 3 4 5 6 7 8 |
|
Чтобы проверить ссылки, вам не нужен Router
для навигации и не нужен <router-outlet>
, чтобы отметить, куда Router
вставляет маршрутизируемые компоненты.
Компоненты BannerComponent
и WelcomeComponent
(обозначаемые <app-banner>
и <app-welcome>
) также не имеют значения.
Тем не менее, любой тест, создающий AppComponent
в DOM, также создает экземпляры этих трех компонентов, и, если вы позволите этому случиться, вам придется настроить TestBed
на их создание.
Если вы не объявите их, компилятор Angular не распознает теги <app-banner>
, <app-welcome>
и <router-outlet>
в шаблоне AppComponent
и выдаст ошибку.
Если вы объявите реальные компоненты, вам также придется объявить их вложенные компоненты и обеспечить все сервисы, внедряемые в любой компонент в дереве.
Это слишком много усилий только для того, чтобы ответить на несколько простых вопросов о связях.
В этом разделе описаны две техники для минимизации настройки. Используйте их по отдельности или в комбинации, чтобы сосредоточиться на тестировании основного компонента.
Создание заглушек ненужных компонентов¶
В первой технике вы создаете и объявляете заглушки компонентов и директив, которые играют незначительную роль или не играют никакой роли в тестах.
1 2 3 4 5 6 7 8 |
|
Селекторы заглушек совпадают с селекторами соответствующих реальных компонентов. Но их шаблоны и классы пусты.
Тогда объявите их в конфигурации TestBed
рядом с компонентами, директивами и пайпами, которые должны быть реальными.
1 2 3 4 5 6 7 8 9 10 |
|
Компонент AppComponent
является объектом тестирования, поэтому, конечно, вы объявляете реальную версию.
Все остальное — это заглушки.
NO_ERRORS_SCHEMA
¶
При втором подходе добавьте NO_ERRORS_SCHEMA
в метаданные TestBed.schemas
.
1 2 3 4 5 6 |
|
Схема NO_ERRORS_SCHEMA
указывает компилятору Angular игнорировать нераспознанные элементы и атрибуты.
Компилятор распознает элемент <app-root>
и атрибут routerLink
, потому что вы объявили соответствующие AppComponent
и RouterLink
в конфигурации TestBed
.
Но компилятор не выдаст ошибку, когда встретит <app-banner>
, <app-welcome>
или <router-outlet>
. Он просто отображает их как пустые теги, и браузер их игнорирует.
Вам больше не нужны компоненты-заглушки.
Используйте обе техники вместе.¶
Это техники для Shallow Component Testing, названные так потому, что они уменьшают визуальную поверхность компонента только до тех элементов в шаблоне компонента, которые важны для тестов.
Подход NO_ERRORS_SCHEMA
является более простым из двух, но не злоупотребляйте им.
Использование NO_ERRORS_SCHEMA
также не позволит компилятору сообщить вам о недостающих компонентах и атрибутах, которые вы случайно пропустили или неправильно написали. Вы можете потратить часы в погоне за фантомными ошибками, которые компилятор обнаружил бы мгновенно.
У подхода компонента-заглушки есть еще одно преимущество. Хотя заглушки в этом примере были пустыми, вы можете дать им урезанные шаблоны и классы, если ваши тесты должны каким-то образом взаимодействовать с ними.
На практике вы будете сочетать эти два метода в одной установке, как показано в этом примере.
1 2 3 4 5 6 |
|
Компилятор Angular создает BannerStubComponent
для элемента <app-banner>
и применяет RouterLink
к якорям с атрибутом routerLink
, но игнорирует теги <app-welcome>
и <router-outlet>
.
By.directive
и инжектируемые директивы¶
Еще немного настроек запускают начальное связывание данных и получают ссылки на навигационные ссылки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Три точки, представляющие особый интерес:
- Найдите элементы якоря с вложенной директивой с помощью
By.directive
. - Запрос возвращает обертки
DebugElement
вокруг соответствующих элементов. - Каждый
DebugElement
раскрывает инжектор зависимости с конкретным экземпляром директивы, прикрепленной к этому элементу.
Ссылки AppComponent
для проверки следующие:
1 2 3 4 5 |
|
Вот несколько тестов, которые подтверждают, что эти ссылки подключены к директивам routerLink
, как и ожидалось:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Использование объекта page
¶
Компонент HeroDetailComponent
— это простое представление с заголовком, двумя полями героя и двумя кнопками.
Но даже в этой простой форме есть много сложностей с шаблонами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Тесты, проверяющие работу компонента, должны …
- Дождаться появления героя, прежде чем элементы появятся в DOM
- Ссылка на текст заголовка
- Ссылка на поле ввода имени, чтобы проверить и установить его
- Ссылки на две кнопки, чтобы их можно было нажать
- Ссылки на некоторые методы компонентов и маршрутизаторов
Даже такая маленькая форма, как эта, может создать беспорядок из мучительных условных настроек и выбора элементов CSS.
Справиться со сложностью можно с помощью класса Page
, который обрабатывает доступ к свойствам компонента и инкапсулирует логику, которая их устанавливает.
Вот такой класс Page
для hero-detail.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 31 |
|
Теперь важные хуки для манипулирования компонентом и его проверки аккуратно организованы и доступны из экземпляра Page
.
Метод createComponent
создает объект page
и заполняет пустые места, когда появляется герой
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Вот еще несколько тестов HeroDetailComponent
, чтобы усилить суть.
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 54 55 56 57 |
|
Вызов compileComponents()
¶
Игнорируйте этот раздел, если вы только запускаете тесты с помощью команды CLI ng test
, поскольку CLI компилирует приложение перед запуском тестов.
Если вы запускаете тесты в не-CLI-среде, тесты могут завершиться неудачей с сообщением, подобным этому:
1 2 3 |
|
Корень проблемы в том, что хотя бы один из компонентов, участвующих в тестировании, указывает внешний шаблон или CSS файл, как это делает следующая версия BannerComponent
.
1 2 3 4 5 6 7 8 9 10 |
|
Тест терпит неудачу, когда TestBed
пытается создать компонент.
1 2 3 4 5 6 |
|
Напомним, что приложение не было скомпилировано. Поэтому, когда вы вызываете createComponent()
, TestBed
компилируется неявно.
Это не проблема, когда исходный код находится в памяти. Но BannerComponent
требует внешних файлов, которые компилятор должен читать из файловой системы, что по своей сути является асинхронной операцией.
Если бы TestBed
было разрешено продолжить работу, тесты запустились бы и загадочно провалились, прежде чем компилятор смог бы закончить работу.
Вытесняющее сообщение об ошибке говорит вам о необходимости явной компиляции с помощью compileComponents()
.
compileComponents()
является асинхронным¶
Вы должны вызывать compileComponents()
внутри асинхронной тестовой функции.
Если вы пренебрежете тем, чтобы сделать тестовую функцию асинхронной (например, забудете использовать waitForAsync()
, как описано), вы увидите следующее сообщение об ошибке
1 |
|
Типичным подходом является разделение логики настройки на две отдельные функции beforeEach()
:
Функции | Подробности |
---|---|
Асинхронная beforeEach() | Компиляция компонентов |
Синхронная beforeEach() | Выполняет оставшуюся настройку |
Асинхронный beforeEach
¶
Напишите первый асинхронный beforeEach
следующим образом.
1 2 3 4 5 |
|
Метод TestBed.configureTestingModule()
возвращает класс TestBed
, чтобы вы могли цепочкой вызывать другие статические методы TestBed
, такие как compileComponents()
.
В этом примере BannerComponent
является единственным компонентом, который будет скомпилирован. В других примерах модуль тестирования конфигурируется с несколькими компонентами и может импортировать прикладные модули, содержащие еще больше компонентов.
Для любого из них могут потребоваться внешние файлы.
Метод TestBed.compileComponents
асинхронно компилирует все компоненты, настроенные в модуле тестирования.
Не переконфигурируйте TestBed
после вызова compileComponents()
.
Вызов compileComponents()
закрывает текущий экземпляр TestBed
для дальнейшей конфигурации. Вы не можете больше вызывать никаких методов конфигурации TestBed
, ни configureTestingModule()
, ни каких-либо методов override...
. При попытке вызова TestBed
выдает ошибку.
Сделайте compileComponents()
последним шагом перед вызовом TestBed.createComponent()
.
Синхронный beforeEach
¶
Вторая, синхронная beforeEach()
содержит оставшиеся шаги настройки, которые включают создание компонента и запрос элементов для проверки.
1 2 3 4 5 |
|
Рассчитывает на то, что бегунок теста будет ждать завершения первого асинхронного beforeEach
перед вызовом второго.
Консолидированная настройка¶
Вы можете объединить две функции beforeEach()
в одну асинхронную beforeEach()
.
Метод compileComponents()
возвращает обещание, поэтому вы можете выполнять синхронные задачи настройки после компиляции, перемещая синхронный код после ключевого слова await
, где обещание было разрешено.
1 2 3 4 5 6 7 8 |
|
compileComponents()
безвреден¶
Нет никакого вреда в вызове compileComponents()
, когда это не требуется.
Файл теста компонентов, сгенерированный CLI, вызывает compileComponents()
, хотя он никогда не требуется при запуске ng test
.
Тесты в этом руководстве вызывают compileComponents
только тогда, когда это необходимо.
Настройка с импортом модуля¶
В предыдущих компонентных тестах модуль тестирования конфигурировался с помощью нескольких деклараций
, например, так:
1 2 3 |
|
Компонент DashboardComponent
прост. Он не нуждается в помощи.
Но более сложные компоненты часто зависят от других компонентов, директив, пайпов и провайдеров, и они тоже должны быть добавлены в модуль тестирования.
К счастью, параметр TestBed.configureTestingModule
параллелен метаданным, передаваемым декоратору @NgModule
, что означает, что вы также можете указать провайдеры
и импорты
.
Компонент HeroDetailComponent
требует много помощи, несмотря на свой небольшой размер и простую конструкцию. В дополнение к поддержке, которую он получает от модуля тестирования по умолчанию CommonModule
, ему необходимы:
NgModel
и друзья вFormsModule
для обеспечения двустороннего связывания данныхTitleCasePipe
из папкиshared
- Службы маршрутизатора
- Службы доступа к данным Hero
Один из подходов заключается в настройке модуля тестирования из отдельных частей, как в этом примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Обратите внимание, что beforeEach()
является асинхронным и вызывает TestBed.compileComponents
, поскольку HeroDetailComponent
имеет внешний шаблон и css-файл.
Как объясняется в Вызов compileComponents()
, эти тесты могут быть запущены в среде без CLI, где Angular придется компилировать их в браузере.
Импорт общего модуля¶
Поскольку многие компоненты приложения нуждаются в FormsModule
и TitleCasePipe
, разработчик создал SharedModule
для объединения этих и других часто запрашиваемых частей.
Тестовая конфигурация тоже может использовать SharedModule
, как показано в этой альтернативной установке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Он немного более плотный и компактный, с меньшим количеством операторов импорта, которые не показаны в этом примере.
Импорт функционального модуля¶
Компонент HeroDetailComponent
является частью HeroModule
Feature Module, который объединяет большее количество взаимозависимых частей, включая SharedModule
. Попробуйте тестовую конфигурацию, которая импортирует HeroModule
, как эта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Остаются только тестовые двойники в providers
. Даже объявление HeroDetailComponent
исчезло.
На самом деле, если вы попытаетесь объявить его, Angular выдаст ошибку, потому что HeroDetailComponent
объявлен и в HeroModule
, и в DynamicTestModule
, созданном TestBed
.
Импорт функционального модуля компонента может быть лучшим способом настройки тестов, если в модуле много взаимных зависимостей, а сам модуль небольшой, как это обычно бывает с функциональными модулями.
Переопределение провайдеров компонента¶
Компонент HeroDetailComponent
предоставляет свой собственный HeroDetailService
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Невозможно заглушить HeroDetailService
компонента в providers
модуля TestBed.configureTestingModule
. Это провайдеры для модуля тестирования, а не для компонента.
Они подготавливают инжектор зависимостей на уровне компонента.
Angular создает компонент со своим собственным инжектором, который является дочерним инжектором приспособления. Он регистрирует провайдеров компонента (в данном случае HeroDetailService
) с дочерним инжектором.
Тест не может получить доступ к сервисам дочернего инжектора из инжектора приспособления. И TestBed.configureTestingModule
также не может их сконфигурировать.
Angular все это время создавал новые экземпляры настоящего HeroDetailService
!
Эти тесты могут завершиться неудачей или таймаутом, если HeroDetailService
выполняет собственные XHR-вызовы к удаленному серверу. А удаленного сервера может и не быть.
К счастью, HeroDetailService
делегирует ответственность за удаленный доступ к данным инжектированному HeroService
.
1 2 3 4 5 |
|
В предыдущей тестовой конфигурации настоящий HeroService
заменен на TestHeroService
, который перехватывает запросы сервера и подделывает их ответы.
Что, если вам не повезло. Что если подделать HeroService
сложно? Что если HeroDetailService
делает собственные запросы к серверу?
Метод TestBed.overrideComponent
может заменить провайдеры
компонента на простые в управлении тестовые двойники, как показано в следующем варианте установки:
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 |
|
Обратите внимание, что TestBed.configureTestingModule
больше не предоставляет поддельный HeroService
, потому что он не нужен.
Метод overrideComponent
¶
Сфокусируйтесь на методе overrideComponent
.
1 2 3 |
|
Он принимает два аргумента: тип компонента для переопределения (HeroDetailComponent
) и объект метаданных переопределения. Объект метаданных override metadata object является общим, определяемым следующим образом:
1 2 3 4 5 |
|
Объект переопределения метаданных может либо добавлять и удалять элементы в свойствах метаданных, либо полностью сбрасывать эти свойства. В этом примере сбрасываются метаданные providers
компонента.
Параметр типа, T
, является типом метаданных, которые вы передадите декоратору @Component
:
1 2 3 4 5 |
|
Предоставление заглушки шпиона (HeroDetailServiceSpy
)¶
Этот пример полностью заменяет массив providers
компонента на новый массив, содержащий HeroDetailServiceSpy
.
HeroDetailServiceSpy
— это заглушка настоящей версии HeroDetailService
, которая имитирует все необходимые функции этого сервиса. Он не инжектирует и не делегирует на более низкий уровень HeroService
, поэтому нет необходимости предоставлять тестовый дубль для этого.
Соответствующие тесты HeroDetailComponent
будут утверждать, что методы HeroDetailService
были вызваны путем шпионажа за методами сервиса. Соответственно, заглушка реализует свои методы как шпионы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Тесты переопределения¶
Теперь тесты могут управлять героем компонента напрямую, манипулируя testHero
в spy-stub, и подтверждать, что методы сервиса были вызваны.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
|
Больше переопределений¶
Метод TestBed.overrideComponent
может быть вызван несколько раз для одного и того же или разных компонентов. В TestBed
есть аналогичные методы overrideDirective
, overrideModule
и overridePipe
для поиска и замены частей этих других классов.
Исследуйте варианты и комбинации самостоятельно.