Модуль 8 · урок 2 з 9
Jasmine базово
Jasmine — це кілька функцій, які треба знати напамʼять. Їх справді мало: опис групи, опис тесту, перевірка й підготовка. Усе інше — деталі, які гуглять за потреби.
Кістяк
describe('compareBy', () => { // група тестів
it('сортує числа за зростанням', () => { // один тест
const rows = [{ n: 3 }, { n: 1 }, { n: 2 }];
const sorted = [...rows].sort(compareBy([{ key: 'n', dir: 'asc' }]));
expect(sorted.map(r => r.n)).toEqual([1, 2, 3]); // перевірка
});
});
Читається як describe плюс it: «compareBy сортує числа
за зростанням». Коли тест падає в CI, ти бачиш саме цей рядок — і одразу розумієш,
що зламалось. Назви на кшталт «test 1» або «works» роблять падіння марним.
Три частини кожного тесту
it('позначає прострочені задачі', () => {
// 1. Підготувати
const task = { dueDate: '2020-01-01' };
// 2. Виконати
const result = isOverdue(task, new Date('2026-01-01'));
// 3. Перевірити
expect(result).toBe(true);
});
Ця структура — не формальність. Якщо частини змішані, тест важко читати, а коли він упаде, доведеться розбиратись, що саме тут перевірялось. Порожній рядок між блоками робить більше для читабельності, ніж коментарі.
Матчери, яких вистачає
expect(value).toBe(5); // строга рівність ===
expect(obj).toEqual({ a: 1 }); // глибоке порівняння за значенням
expect(list).toContain('kyiv');
expect(list.length).toBe(3);
expect(value).toBeTruthy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
expect(n).toBeGreaterThan(10);
expect(text).toMatch(/^KYIV-/);
expect(() => parse('...')).toThrow();
expect(() => parse('...')).toThrowError('Невірний формат');
expect(value).not.toBe(5); // заперечення будь-якого матчера
toBe — це ===, тобто для обʼєктів порівнює посилання.
expect({ a: 1 }).toBe({ a: 1 }) завжди провалиться. Для обʼєктів і масивів
потрібен toEqual. Та сама історія про посилання, що ламала OnPush у модулі 3 —
тут вона повертається у вигляді загадкового падіння тесту.
Підготовка й прибирання
describe('TaskStore', () => {
let store: TaskStore;
let api: jasmine.SpyObj<TaskApi>;
beforeEach(() => { // перед КОЖНИМ тестом
api = jasmine.createSpyObj<TaskApi>('TaskApi', ['list', 'create']);
store = new TaskStore(api);
});
afterEach(() => { … }); // після кожного
it('…', () => { … });
});
Головне тут — слово кожним. Стан не переноситься між тестами, і це навмисно: тести мають проходити в будь-якому порядку й поодинці. Якщо тест падає лише разом з іншими, десь протік спільний стан.
Спаї
// підробка цілого обʼєкта
const api = jasmine.createSpyObj<TaskApi>('TaskApi', ['list', 'create']);
api.list.and.returnValue(of({ items: [], total: 0 }));
// перевірити виклик
expect(api.create).toHaveBeenCalled();
expect(api.create).toHaveBeenCalledWith({ code: 'KYIV-1' });
expect(api.create).toHaveBeenCalledTimes(1);
expect(api.list).not.toHaveBeenCalled();
// стежити за справжнім методом, не підміняючи його
spyOn(console, 'error');
// різні відповіді на послідовні виклики
api.list.and.returnValues(of(page1), of(page2));
// змусити впасти
api.create.and.returnValue(throwError(() => new Error('boom')));
Фокус і пропуск
fdescribe('…') // запустити ТІЛЬКИ цю групу
fit('…') // запустити ТІЛЬКИ цей тест
xdescribe('…') // пропустити групу
xit('…') // пропустити тест
fit
fit і fdescribe зручні під час налагодження, але якщо такий
рядок потрапить у коміт, у CI мовчки прогониться один тест замість усіх — і збірка
буде зеленою. Це реальний випадок, який трапляється регулярно. Тому ESLint-правило
jasmine/no-focused-tests варто ввімкнути ще до першого тесту.