Коротка відповідь. На live coding тести – це не формальність, а найшвидший спосіб показати інженерне мислення. Навіть якщо середовище співбесіди не має тест-раннера, кілька рядків із console.assert демонструють: ти думаєш про крайні випадки до того, як інтерв'юер про них спитає. Нижче – мінімальний патерн без фреймворка, той самий підхід у синтаксисі vitest/jest і готові тест-кейси для двох класичних задач.

Чому тести підвищують оцінку

Інтерв'юер за 40 хвилин оцінює не лише «працює/не працює», а як ти мислиш. Кандидат, який після happy path каже «тепер перевірю порожній масив і від'ємні значення» і робить це кодом, закриває одразу три пункти оцінки: розуміння вимог, увагу до країв і звичку до самоперевірки. Це працює на будь-якому рівні – від Junior до Senior, різниця лише в глибині кейсів.

Мінімальний патерн без фреймворка

У більшості онлайн-редакторів співбесід немає jest. Достатньо console.assert:

function groupBy(items, keyFn) {
  const groups = new Map();
  for (const item of items) {
    const key = keyFn(item);
    const bucket = groups.get(key) ?? [];
    bucket.push(item);
    groups.set(key, bucket);
  }
  return groups;
}

// Тести: assert мовчить, якщо все добре, і кричить, якщо ні
const byLength = groupBy(['ай', 'бо', 'кіт'], (word) => word.length);
console.assert(byLength.get(2).length === 2, 'два слова довжини 2');
console.assert(byLength.get(3).length === 1, 'одне слово довжини 3');

const empty = groupBy([], (x) => x);
console.assert(empty.size === 0, 'порожній вхід -> порожній результат');

Правило: один assert – одне твердження з підписом. Підпис важливий: коли щось упаде, ти одразу бачиш що саме, і не витрачаєш хвилини співбесіди на дебаг власних тестів.

Той самий підхід у vitest/jest

Якщо середовище дозволяє (або тебе просять «як би ти це тестував у проєкті»), синтаксис майже однаковий у vitest і jest:

import { describe, expect, it } from 'vitest';

describe('groupBy', () => {
  it('групує за обчисленим ключем', () => {
    const result = groupBy(['ай', 'бо', 'кіт'], (word) => word.length);
    expect(result.get(2)).toEqual(['ай', 'бо']);
    expect(result.get(3)).toEqual(['кіт']);
  });

  it('повертає порожню мапу для порожнього входу', () => {
    expect(groupBy([], (x) => x).size).toBe(0);
  });

  it('не мутує вхідний масив', () => {
    const input = ['a', 'b'];
    groupBy(input, (x) => x);
    expect(input).toEqual(['a', 'b']);
  });
});

Третій тест – «не мутує вхід» – майже ніхто не пише, а інтерв'юери його люблять: він показує розуміння побічних ефектів.

Табличні тести: багато кейсів без копіпасти

Коли кейсів більше трьох, таблиця читається краще за серію it-блоків:

it.each([
  { input: [1, 2, 2, 3], expected: [1, 2, 3] },
  { input: [], expected: [] },
  { input: [1, 1, 1], expected: [1] },
  { input: ['1', 1], expected: ['1', 1] }, // типи не змішуються
])('unique($input) -> $expected', ({ input, expected }) => {
  expect(unique(input)).toEqual(expected);
});

Останній рядок таблиці – хороший приклад «підступного» кейсу: рядок '1' і число 1 мають лишитися різними значеннями. Саме такі рядки в таблиці показують, що ти думаєш про типи, а не тільки про щасливий шлях.

Складніший випадок: тестуємо debounce

debounce – задача з часом, тому потрібні фейкові таймери:

import { afterEach, beforeEach, expect, it, vi } from 'vitest';

beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());

it('викликає функцію один раз після паузи', () => {
  const spy = vi.fn();
  const debounced = debounce(spy, 300);

  debounced('a');
  debounced('b');
  debounced('c');

  expect(spy).not.toHaveBeenCalled();     // ще чекаємо
  vi.advanceTimersByTime(300);

  expect(spy).toHaveBeenCalledTimes(1);   // один виклик...
  expect(spy).toHaveBeenLastCalledWith('c'); // ...з останнім аргументом
});

Два твердження в кінці – суть debounce: не тільки «викликано один раз», а й «з останнім аргументом». Якщо на співбесіді немає vitest, ту саму ідею можна показати через setTimeout у демо-виклику і коментар, що саме ти б перевірив.

Тест до коду: полегшена версія TDD для співбесіди

Повноцінний TDD-цикл на співбесіді зазвичай не потрібен, але один прийом із нього дуже практичний: запиши перший тест до реалізації – він фіксує контракт і знімає двозначність умови.

// Умова: «згладь вкладений масив на один рівень»
// Перш ніж писати flatten, фіксуємо, що це означає:
console.assert(
  JSON.stringify(flattenOnce([1, [2, 3], [4, [5]]])) ===
    JSON.stringify([1, 2, 3, 4, [5]]),
  'вкладеність зменшується рівно на один рівень'
);

Цей assert ще до реалізації виявляє головне непорозуміння задачі: [5] лишається масивом чи ні? Якщо твоє розуміння розходиться з очікуванням інтерв'юера – ви з'ясуєте це за 30 секунд на тесті, а не за 15 хвилин на готовому коді. Фраза «давайте я зафіксую очікуваний результат прикладом» ще жодного кандидата не зробила слабшим.

Чекліст крайніх кейсів за типом задачі

Швидка шпаргалка, що перевіряти залежно від того, з чим працює функція:

Тип входу Обов'язкові кейси
Масив порожній; один елемент; усі елементи однакові; дублікати
Рядок порожній; один символ; пробіли; юнікод/кирилиця
Число 0; від'ємне; дробове; дуже велике
Об'єкт відсутнє поле; null у полі; зайві поля
Колбек кидає виняток; повертає undefined; викликається 0 разів
Час (debounce/throttle) кілька викликів поспіль; виклик після паузи; аргументи останнього виклику

Не треба покривати всю таблицю на кожній задачі – обери 2-3 рядки, релевантні умові, і назви решту вголос: «за наявності часу я б ще перевірив X та Y».

Як промовляти тест-стратегію вголос

Формула з чотирьох речень, яка працює для будь-якої задачі:

  1. «Спочатку перевірю основний сценарій – те, заради чого функція існує.»
  2. «Потім порожні й мінімальні входи: порожній масив, один елемент.»
  3. «Далі межі контракту: дублікати, невалідні типи, від'ємні числа – залежно від умови.»
  4. «І окремо – відсутність побічних ефектів: вхідні дані не мутуються.»

Навіть якщо встигнеш написати лише половину цих тестів, озвучена стратегія вже зарахована.

Куди далі

Набір задач, до яких варто застосувати цей підхід, – у добірці JavaScript live coding; для фронтендерів – React-задачі з TypeScript. Як вбудувати тести в загальний план підготовки по днях – у статті про підготовку до співбесіди за 30 днів.

Практичне завдання: візьми останню розв'язану задачу і додай до неї п'ять тестів за формулою вище. Якщо хоч один упав – ти щойно знайшов баг, який знайшов би інтерв'юер.