Angular unit тесты — с чего начать?

Про подготовку и запуск тестов я уже писала в посте Jest и Angular 2+ (конечно, вы можете использовать и karma).

В этот раз хотелось бы поговорить о том как их писать на Angular 4+.
И с чем вообще можно столкнуться прям вот сразу, если генерируешь сущности через Angular cli.
Это скорее мой личный небольшой опыт, а не руководство, так что я буду рада всем замечаниям.

При генерации компонентов через angular-cli автоматически создаются файлы тестов, которые проверяют, что компонент успешно создался.
Я с начала разработки конкретно подзабила на тесты, а когда решила их проверить, то оказалось, что они уже совсем не проходят — хотя вроде бы простая проверка на создание компонента.

Это произошло так как я инжектила в компоненты сервисы, добавляла дочерние компоненты и директивы.
А файлы тестов остались в своем исходном виде.

С чего начать?

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

Тогда смело удаляем проверку на создание компонента и пишем так называемый изолированный тест.

Это тест, который не зависит от Angular. И на самом деле его можно считать самым простым во всех смыслах.

На примере сервиса главным отличием является то, как мы получаем сервис для теста.

Рассмотрим примеры из документации.

Изолированный тест выглядит так:

it('#getTimeoutValue should return timeout value',  (done: DoneFn) => {
  service = new FancyService();
  service.getTimeoutValue().then(value => {
    expect(value).toBe('timeout value');
    done();
  });
});

А с помощью тестовой утилиты Angular мы реализуем ту же проверку так:

beforeEach(() => {
  TestBed.configureTestingModule({ providers: [FancyService] });
});

it('test should wait for FancyService.getTimeoutValue',
  async(inject([FancyService], (service: FancyService) => {

  service.getTimeoutValue().then(
    value => expect(value).toBe('timeout value')
  );
})));

Второй вариант выглядит более сложным.

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

И говорят — хороший разработчик пишет оба типа тестов 🙂

А что с дочерними компонентами и директивами?

Так как я решила сохранить проверку создания компонентов, то столкнулась с необходимостью инжектить все зависимые дочерние компоненты и директивы.

Однако, оказалось, что если нет необходимости протестить в данном случае их работу, то достаточно прописать в TestBed утилите NO_ERRORS_SCHEMA:

TestBed.configureTestingModule({
    declarations: [ AppComponent ],
    schemas:      [ NO_ERRORS_SCHEMA ]
  })

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

Такой тип теста называется shallow(поверхностный) — так как мы тестируем только текущий компонент, не уходя в глубь.
И обращу внимание, что изолированным его все же не назовешь.

Также не стоит забывать про pipes, которые тоже требуют определения.

Так в случае пайпов для переводов потребовалось подключать фейковый модуль:

 imports: [ TranslateModule.forRoot({
        loader: { provide: TranslateLoader,
 useClass: TranslateFakeLoader }})],

Сервисы в компонентах

Избавившись от ошибок зависимых компонентов и директив я осталась с ошибками об неопределенных сервисах.
Во-первых, они инжектятся в компонент, а значит их по-любому надо как-то определять в тестах.
А во-вторых, так как в ngOnInit у меня вызываются различные функции сервисов, то при генерации компонента неизбежно встает вопрос — что это вообще за функция? Мы такой не знаем.

На выручку пришли подделки сервисов(stubs). В самом простом случае они могут выглядеть вообще так:

let myServiceStub = {}
beforeEach(() => {
    TestBed.configureTestingModule({
      providers:  [{ provide: myService, useValue:  myServiceStub }],
    });
  });

Это если при рендеринге компонента не вызывается какой-либо метод сервиса.

Если да, тот тут уже приходится прописыватьть функции в объекте:

const gameStorageServiceStub = {
    getGames: () => ({}),
    dataChange: new BehaviorSubject(null)
  };

Кстати, если вы используете Visual Studio то там есть волшебная кнопка генерации теста автоматом.
В WebStorm я такого, увы, не нашла.

На этом пока что все. В следующем посте я планирую уйти в конкретику теста тех или иных случаев.

UPD: следующий более объемный пост я опубликовала на хабре — Angular 5: Unit тесты.

Хотите быть в курсе новых статей?