Модуль 8 · урок 3 з 9
TestBed
TestBed створює міні-застосунок Angular для тесту: з інжектором, провайдерами й можливістю рендерити компоненти. Він потужний і повільний — тому головне вміння тут не «як користуватись», а «коли не користуватись».
Коли він не потрібен
// чиста функція — жодного Angular
it('фільтрує за статусом', () => {
const result = applyState(rows, { status: 'done', page: 1, size: 10, sort: [] });
expect(result.items.length).toBe(3);
});
// клас без інʼєкцій — просто new
it('додає задачу в стан', () => {
const store = new TaskStore(apiSpy);
store.add(task);
expect(store.snapshot().length).toBe(1);
});
Це найшвидші тести в проєкті: мілісекунди замість сотень мілісекунд. Правило просте —
TestBed береш лише тоді, коли без нього неможливо: коли треба рендерити компонент
або коли залежності беруться через inject() у контексті інʼєкції.
Мінімальна конфігурація
import { TestBed } from '@angular/core/testing';
describe('TaskStore', () => {
let store: TaskStore;
let api: jasmine.SpyObj<TaskApi>;
beforeEach(() => {
api = jasmine.createSpyObj<TaskApi>('TaskApi', ['list']);
TestBed.configureTestingModule({
providers: [
TaskStore,
{ provide: TASK_API, useValue: api }, // підміна через токен із модуля 4
],
});
store = TestBed.inject(TaskStore);
});
});
Один рядок { provide: TASK_API, useValue: api } — і замість мок-сервісу
з випадковими збоями підставляється передбачуваний спай. Якби стор створював API через
new, підмінити його було б неможливо, і тест довелось би писати проти
справжніх затримок. Це та сама «архітектура», про яку йшлося в модулі 2 — тут вона
перетворюється на конкретну економію часу.
Тестування HTTP
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
describe('TaskApiHttp', () => {
let api: TaskApiHttp;
let http: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TaskApiHttp, provideHttpClient(), provideHttpClientTesting()],
});
api = TestBed.inject(TaskApiHttp);
http = TestBed.inject(HttpTestingController);
});
afterEach(() => http.verify()); // впаде, якщо лишились неопрацьовані запити
it('запитує список із параметрами', () => {
let result: Page<Task> | undefined;
api.list({ page: 2, size: 25 }).subscribe(r => (result = r));
const req = http.expectOne(r => r.url === '/api/tasks');
expect(req.request.method).toBe('GET');
expect(req.request.params.get('page')).toBe('2');
req.flush({ items: [], total: 0 }); // віддати відповідь
expect(result?.total).toBe(0);
});
it('передає помилку далі', () => {
let error: unknown;
api.list({ page: 1, size: 10 }).subscribe({ error: e => (error = e) });
http.expectOne('/api/tasks').flush('boom', { status: 500, statusText: 'Server Error' });
expect(error).toBeTruthy();
});
});
HttpTestingController перехоплює запити й дає віддати відповідь вручну.
Жодної мережі, жодних затримок, повний контроль — включно з можливістю змоделювати 500
одним рядком.
Рендер компонента
describe('TaskCard', () => {
let fixture: ComponentFixture<TaskCard>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TaskCard], // standalone-компонент іде в imports
}).compileComponents();
fixture = TestBed.createComponent(TaskCard);
});
it('показує назву задачі', () => {
fixture.componentRef.setInput('task', { id: '1', title: 'Зверстати шапку' });
fixture.detectChanges(); // без цього шаблон не оновиться
const title = fixture.nativeElement.querySelector('.card__title');
expect(title.textContent).toContain('Зверстати шапку');
});
});
Для сигнальних input() потрібен саме fixture.componentRef.setInput().
Спроба присвоїти fixture.componentInstance.task = … не спрацює: сигнал
вхідного значення доступний лише для читання. І не забувай detectChanges() —
Angular у тесті не перевіряє зміни сам.
Чому TestBed повільний
Кожен configureTestingModule створює нове середовище, компілює компоненти
й піднімає інжектор. На пʼятдесяти тестах різниця непомітна, на пʼятистах — це хвилини
очікування в CI. Звідси й практичне правило: чим більше логіки в чистих функціях,
тим швидший набір тестів. Ще один аргумент за поділ container і presentation із модуля 3.