Модуль 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— тест повторюватиме шаблон. - Тест перевіряє наявність тексту, який завтра змінить копірайтер.
- Потрібно мокати пʼять сервісів — винось логіку, а не тестуй так.
- Перевіряється верстка: відступи, кольори, порядок елементів.
Чому вони крихкі
Тест компонента залежить від трьох речей одразу: логіки, шаблону й стилів взаємодії. Зміна будь-якої з них ламає тест — часто без реальної проблеми в коді. Через рік такий набір починають вимикати замість виправляти, і це найгірший можливий підсумок.
Практичний висновок для нашого проєкту: один-два тести компонента максимум, і то для чогось справді змістовного. Основну цінність дають тести чистих функцій і стору — вони і швидші, і живуть довше.