Урок по треку Автоматизация тестирования 7 мин

Jest и Vitest за 7 минут

Структура тестов, матчеры, моки, фейковые таймеры, покрытие.

Jest и Vitest это тестовые раннеры для JavaScript и TypeScript. Автоматизатор встречает их, когда пишет unit- и интеграционные тесты вместе с фронтендом или Node-бэкендом, а ещё когда API-тесты живут на TypeScript. API у них почти одинаковый: Vitest сознательно совместим с Jest.

Чем они отличаются

  • Jest: давний стандарт, своя система трансформации (Babel или ts-jest), огромная экосистема.
  • Vitest: работает поверх Vite, переиспользует его конфиг и плагины, нативно понимает ESM и TypeScript, быстро стартует. В новых проектах на Vite его выбирают по умолчанию.

Структура теста

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

describe('Cart', () => {
  let cart: Cart;
  beforeEach(() => { cart = new Cart(); });

  it('считает сумму', () => {
    cart.add({ sku: 'A', price: 100, qty: 2 });
    expect(cart.total()).toBe(200);
  });

  it.each([
    [0, 0], [999, 0], [1000, 100],
  ])('скидка для суммы %i равна %i', (sum, discount) => {
    expect(discountFor(sum)).toBe(discount);
  });
});

Хуки: beforeAll, beforeEach, afterEach, afterAll. Фокус и пропуск: it.only, it.skip, it.todo.

Матчеры, которые путают

  • toBe сравнивает через Object.is: для объектов это проверка ссылки.
  • toEqual рекурсивно сравнивает значения и игнорирует свойства со значением undefined.
  • toStrictEqual строже: учитывает undefined-свойства, дырки в массивах и класс объекта.
  • toMatchObject проверяет подмножество: в объекте должны быть указанные поля, лишние не мешают.
  • toThrow, resolves, rejects для ошибок и промисов.

Асинхронность

Тест должен вернуть промис или быть async, иначе он закончится раньше проверки:

it('загружает профиль', async () => {
  await expect(api.getProfile(1)).resolves.toMatchObject({ id: 1 });
});

Забытый await перед expect(...).resolves частая причина «зелёных» тестов, которые ничего не проверили.

Моки

const send = vi.fn().mockResolvedValue({ ok: true });   // в Jest: jest.fn()
vi.mock('./mailer', () => ({ send }));                   // подмена модуля, поднимается в начало файла
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});

expect(send).toHaveBeenCalledTimes(1);
expect(send).toHaveBeenCalledWith(expect.objectContaining({ to: 'a@b.ru' }));

vi.mock и jest.mock поднимаются (hoisting) над импортами, поэтому внутри фабрики нельзя пользоваться переменными, объявленными ниже обычным способом. Между тестами моки сбрасывают: mockClear (история вызовов), mockReset (плюс реализация), mockRestore (вернуть оригинал для spyOn), или настройками clearMocks, restoreMocks.

Фейковые таймеры

vi.useFakeTimers();
const cb = vi.fn();
debounce(cb, 300)();
vi.advanceTimersByTime(299); expect(cb).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);   expect(cb).toHaveBeenCalledOnce();
vi.useRealTimers();

Тест на 300 мс задержку идёт мгновенно и без флаки.

Снапшоты

toMatchSnapshot() сохраняет сериализованный результат в файл и потом сравнивает. Удобно для больших структур, но легко превращается в «обновил снапшот, не глядя» (-u). Снапшот должен быть маленьким и проверяться на ревью.

Окружение и покрытие

  • Окружение: node по умолчанию; для DOM нужен jsdom или happy-dom (testEnvironment в Jest, environment в Vitest).
  • Покрытие: --coverage. В Vitest провайдер по умолчанию v8, можно istanbul. Пороги (coverageThreshold в Jest, coverage.thresholds в Vitest) роняют прогон, если покрытие ниже.

Частые ошибки

  • toBe для объектов и массивов.
  • Нет await у асинхронной проверки.
  • Моки не сбрасываются между тестами, и тесты влияют друг на друга.
  • Огромные снапшоты, которые обновляют не читая.
  • Настоящие таймеры и setTimeout в тестах вместо фейковых.

Что спросят на собесе

  • Чем toBe отличается от toEqual и toStrictEqual?
  • Как замокать модуль и проверить, с какими аргументами вызвали функцию?
  • Как тестировать код с таймерами?
  • Чем Vitest отличается от Jest?
  • Когда снапшоты полезны, а когда вредны?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.