Angular IDPStrong Junior
Модулі / Юніт-тести / Тест компонента
🔥 0

Модуль 8 · урок 7 з 9

Тест компонента

Тести компонентів — найдорожчі й найкрихкіші в наборі. У таблиці ліда вони не вимагаються, тож цей урок оглядовий: що це таке, коли справді потрібно й чому не варто захоплюватись.

Базовий тест presentation-компонента

describe('TaskCard', () => {
  let fixture: ComponentFixture<TaskCard>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({ imports: [TaskCard] }).compileComponents();
    fixture = TestBed.createComponent(TaskCard);
  });

  it('показує назву задачі', () => {
    fixture.componentRef.setInput('task', { id: '1', title: 'Зверстати шапку' });
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).toContain('Зверстати шапку');
  });

  it('емітить подію при кліку', () => {
    fixture.componentRef.setInput('task', task);
    fixture.detectChanges();

    let picked: Task | undefined;
    fixture.componentInstance.selected.subscribe(t => (picked = t));

    fixture.nativeElement.querySelector('button').click();

    expect(picked).toBe(task);
  });
});

Ось за що ми боролись у модулі 3: presentation-компонент тестується без жодного мока. Подав input — перевірив розмітку. Клікнув — перевірив output. Якщо для тесту компонента доводиться мокати три сервіси, це сигнал, що він робить забагато.

Пошук елементів

// нативний DOM — простіше й ближче до реальності
const btn = fixture.nativeElement.querySelector('[data-test="save"]');

// DebugElement — дає доступ до контексту Angular
const de = fixture.debugElement.query(By.css('.card__title'));
const child = fixture.debugElement.query(By.directive(TaskBadge));
Атрибут data-test

Шукати елементи за класами — погана ідея: клас змінить дизайнер, і тест упаде без причини. Окремий атрибут data-test="save" існує рівно для тестів, тож його ніхто не чіпатиме випадково. У продакшн-збірці його можна вирізати, але зазвичай просто лишають.

Харнеси Material

import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';

it('вимикає кнопку під час збереження', async () => {
  const loader = TestbedHarnessEnvironment.loader(fixture);
  const button = await loader.getHarness(MatButtonHarness.with({ text: 'Зберегти' }));

  expect(await button.isDisabled()).toBe(false);

  component.saving.set(true);
  fixture.detectChanges();

  expect(await button.isDisabled()).toBe(true);
});

Харнес — це прошарок, який знає внутрішню будову компонента Material. Перевага в тому, що коли Material змінить свою розмітку в наступній версії, харнес оновиться разом із бібліотекою, а твій тест лишиться робочим. Селектор .mat-mdc-button-disabled у тесті таку зміну не переживе.

Коли тест компонента виправданий

Варто
  • Компонент має нетривіальну логіку відображення: умови, гілки станів.
  • Це перевикористовний presentation-компонент із чітким контрактом.
  • У ньому був баг, і треба зафіксувати, що він не повернеться.
  • Складна взаємодія: перетягування, гарячі клавіші.
Не варто
  • Компонент лише показує input — тест повторюватиме шаблон.
  • Тест перевіряє наявність тексту, який завтра змінить копірайтер.
  • Потрібно мокати пʼять сервісів — винось логіку, а не тестуй так.
  • Перевіряється верстка: відступи, кольори, порядок елементів.

Чому вони крихкі

Тест компонента залежить від трьох речей одразу: логіки, шаблону й стилів взаємодії. Зміна будь-якої з них ламає тест — часто без реальної проблеми в коді. Через рік такий набір починають вимикати замість виправляти, і це найгірший можливий підсумок.

Практичний висновок для нашого проєкту: один-два тести компонента максимум, і то для чогось справді змістовного. Основну цінність дають тести чистих функцій і стору — вони і швидші, і живуть довше.