Unit-тесты для приложения на Angular 2+

6 min readMay 23, 2020

Unit-тестирование — это важная часть процесса разработки на текущий момент и она становится неотъемлемой частью. Это повышает качество кода и уверенность разработчиков. В данной статье я расскажу о том, как создавать unit-тесты для приложения на Angular, что использовать и почему.

Для написания unit-тестов используется фреймворк Jasmine, а выполняются тесты в Karma. Karma — это JavaScript движок для запуска тестов, выполняемых в браузере.

Есть 2 основные концепции тестирования TDD и BDD:

  • TDD (test driven development) — разработка на основе тестов. Сначала пишем тесты, а потом под них пишем код. Концепция ориентирована на проверку каждого блока кода, написанного разработчиком. Именно для этого и используются unit-тесты.
  • BDD (behavior driven development) — разработка на основе поведения. Это ответвление от TDD. Концепция ориентирована на проверку бизнес-сценариев, реализованных в коде.

Фреймворк Jasmine прекрасно подходит и для TDD, и для BDD.

Особенности unit-тестов

  • Каждый тест должен выполняться очень быстро
  • Каждый тест должен быть изолирован от всех зависимостей
  • Тест должен иметь четко определенный результат выполнения для каждого набора входных данных
  • Наименьший блок кода (unit) может быть проверен отдельным тестом

Когда мы пишем unit-тесты

  • В самом начале или в конце разработки
  • Если мы добавили новую фичу в коде, мы должны покрыть ее работу тестом
  • Если мы изменили существующую фичу в коде, мы также должны изменить/добавить для нее тест

Почему стоит использовать Jasmine

  • По-умолчанию интегрирована с Karma
  • Предоставляет удобный функционал, например, Spy, fake
  • Легко интегрируется с отчетностью

Почему стоит использовать Karma

  • Angular CLI умеет работать с Karma из коробки.
  • Позволяет автоматизировать тесты для разных браузеров и девайсов.
  • Следит за изменениями файлов без ручного перезапуска
  • Хорошо задокументирована
  • Легко интегрируется с CI-серверами

Пример проекта

Для запуска тестирования на Angular не нужно ничего настраивать. Благодаря Angular CLI все настройки (Jasmine и Karma) создаются автоматически.

// создаем новый Angular проект
ng new angular-unit-testing
// запускаем тестирование
npm test

И вы сразу же увидите результат в браузере:

Для тестирования различных особенностей Angular я создала пример проекта. Это простой to-do список. Ниже скриншот и ссылка на проект.

// клонируем отсюда
git clone https://github.com/irivanych/angular-unit-testing.git
// устанавливаем зависимости и запускаем
npm install
npm start

Конфигурация тестов в Angular

Когда мы запускаем npm test или ng test, Angular использует настройки из файла angular.json. Обратите внимание на test-параметр этого файла.

Мы указали test.ts как основной файл для запуска и karma.conf.js как конфигурацию для Karma.

В файле test.ts мы загружаем все файлы проекта с расширением .spec.ts для тестирования.

А в файле karma.conf.js мы определяем настройки отчетов, порт, типы браузеров для тестирования и тд.

Основные функции

TestBed

Это базовый блок для создания тестового модуля. TestBed создает динамический тестовый модуль, который эмулирует работу NgModule. Входные данные для TestBed.configureTestingModule() и @NgModule совершенно одинаковые. И именно TestBed.configureTestingModule() является конфигурацией тестового файла.

Конфигурация всегда помещается в beforeEach() функцию, которая запускается перед каждым тестом.

compileComponents()

В этом же файле мы используем compileComponents() метод. Если вы создаете компонент, использующий внешние файлы для определения styleUrls, templateUrl, тогда вы должны получить эти файлы из файловой системы до того, как компонент будет создан. Это асинхронная операция и потому мы оборачиваем конфигурацию в асинхронную функцию.

Фактически происходит следующее: при запуске тестирования подтягивается внешний файл, а затем начинается компиляция компонента. Если вы запускаете тесты с Angular CLI, тогда асинхронная функция не нужна. Поскольку CLI компилирует весь код до запуска тестов.

Почему две beforeEach() функции

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

Зачем использовать NO_ERRORS_SCHEMA

Давайте выберем app.component.ts компонент из проекта и протестируем его. Посмотрите на spec файл, который Angular CLI сгенерировал для нас, когда мы выполнили команду

ng n c

Я добавила f(строка 5) к функции describe(), чтобы выполнять только этот файл во время тестирования. Это называется Focused Tests.

Запустив npm test, мы увидим много ошибок из-за вложенности компонентов, потому что основной компонент содержит другие компоненты, такие как header, footer. Мы должны объявить их в app.component.spec.ts в массиве declarations (строка 11).

Другой способ — использование NO_ERRORS_SCHEMA. Эта настройка разрешает ангуляру игнорировать все неизвестные теги при тестировании компонента.

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

В нашем компоненте есть метод deleteItem(). Давайте его протестируем.

Тестирование DOM

Давайте протестируем footer компонент, в котором у нас есть footerText и numberOfItems. Изменим их в компоненте и проверим шаблон footer.

Обратите внимание на fixture.detectChanges(). Мы изменили footerText и numberOfItems в тесте и вызвали механизм обнаружения изменений ангуляра для того, чтобы DOM был обновлен в соответствии с изменениями до запуска теста.

Ниже результат теста в браузере. Поскольку footer является автономным компонентом, мы видим результат рендеринга в Karma браузере.

Тестирование pipe

Тестировать Angular pipe очень просто, поскольку они не имеют зависимостей. У меня есть pipe, который показывает текущий год после текста в footer. Давайте его протестируем:

Тестирование сервисов

Сервисы тоже довольно просто тестировать. У нас есть один сервис в приложении. Давайте протестируем метод addItem() в сервисе:

Покрытие кода

Покрытие кода показывает как много процентов кода покрыто unit-тестами. Обычно хорошим показателем является 80% покрытия.

Angular CLI позволяет сгенерировать отчет о покрытии приложения тестами в отдельной директории, называемой coverage. Чтобы сгенерировать отчет нужно запустить тесты с флагом — code-coverage.

ng test — no-watch — code-coverage

Если вы запустите index.html из этой папки, то увидите полный отчет.

Практические советы

  • Убедитесь, что тест запускается в изолированном окружении, без внешних зависимостей. Тогда он будет выполняться быстро.
  • Убедитесь, что ваше приложение покрыто как минимум на 80% кода тестами.
  • Когда тестируете сервисы, всегда используйте spy из Jasmine фреймворка для зависимостей. Это позволит выполнять тесты намного быстрее.
  • Когда вы подписываетесь на Observable во время теста, убедитесь, что вы обрабатываете и success, и failure результат. Это избавит вас от ненужных ошибок.
  • Когда тестируете компонент с сервис-зависимостями, всегда используйте mock сервис вместо реального сервиса.
  • Все конфигурации TestBedConfiguration должны быть в beforeEach, чтобы не дублировать одинаковый код для каждого теста.
  • когда тестируете компоненты, всегда обращайтесь к DOM через debugElement вместо nativeElement. Потому что debugElement предоставляет абстрактный слой для среды выполнения. Это уменьшает количество ошибок.
  • Используйте By.css вместо queryselector, когда запускаете тестирование на сервере. Дело в том, что queryselector работает только в браузере. Если вы запустите тестирование на сервере, то оно упадет.
  • Всегда используйте fixture.detectChanges() вместо ComponentFixtureAutoDetect. ComponentFixtureAutoDetect не знает о синхронном обновлении компонента.
  • Вызывайте compileComponents(), если вы запускаете тестирование не в CLI среде.
  • Используйте RXJS marble тестирование всегда, когда тестируете observable.
  • Используйте PageObject модель для переиспользования функций между компонентами.
  • Не злоупотребляйте NO_ERRORS_SCHEMA. Эта настройка разрешает ангуляру игнорировать атрибуты и нераспознанные теги. Используйте заглушки компонентов.
  • Никогда не выполняйте никаких настроек после вызова compileComponents(). compileComponents() является асинхронным и должен выполняться в async функции внутри beforeEach. Последующие настройки выполняйте в следующем синхронном beforeEach.

Заключение

Использовать unit-тесты в Angular приложении очень удобно, если вы понимаете базовые принципы тестирования Angular, Jasmine и Karma.

--

--

No responses yet