Angular IDPStrong Junior
Модулі / Юніт-тести / Jasmine базово
🔥 0

Модуль 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 чи toEqual

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 варто ввімкнути ще до першого тесту.

≈ 45 хв · +25 XP за урок