Vitest

Vitest — фреймворк тестирования, нативно интегрированный с Vite. Быстрый, совместимый с Jest API, поддерживает ESM из коробки.

Зачем нужно

Jest создавался для CommonJS-мира и требует трансформации для ESM/TypeScript. Vitest использует конвейер Vite — тот же конфиг, та же трансформация, мгновенный HMR для тестов. Если проект на Vite, Vitest — естественный выбор.

Где используется

Проекты на Vite (Vue, React, Svelte), библиотеки с ESM, новые проекты без legacy CJS-зависимостей.

Предпосылки

Настройка Jest, Основы тестирования, знакомство с Vite

Установка

npm install -D vitest

Конфигурация в vite.config.ts

// vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vite';

export default defineConfig({
  test: {
    globals: true,           // describe, test, expect без импортов
    environment: 'jsdom',    // 'node' | 'jsdom' | 'happy-dom'
    include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
    coverage: {
      provider: 'v8',        // 'v8' или 'istanbul'
      reporter: ['text', 'lcov'],
      include: ['src/**/*.{js,ts}'],
      exclude: ['src/**/*.test.{js,ts}'],
    },
    setupFiles: ['./src/test/setup.ts'],
  },
});

Или отдельный vitest.config.ts

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
  },
});

Скрипты в package.json

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "test:watch": "vitest --watch",
    "test:coverage": "vitest run --coverage",
    "test:ui": "vitest --ui"
  }
}

Совместимость с Jest API

Vitest повторяет API Jest почти полностью:

// Работает и в Jest, и в Vitest
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';

describe('Calculator', () => {
  test('складывает числа', () => {
    expect(1 + 2).toBe(3);
  });

  test('объекты', () => {
    expect({ a: 1 }).toEqual({ a: 1 });
  });

  test('массивы', () => {
    expect([1, 2, 3]).toContain(2);
  });

  test('исключения', () => {
    expect(() => { throw new Error('oops'); }).toThrow('oops');
  });
});

vi вместо jest

// Jest:
const fn = jest.fn;
jest.mock('./module');
jest.spyOn(obj, 'method');
jest.useFakeTimers;

// Vitest:
const fn = vi.fn;
vi.mock('./module');
vi.spyOn(obj, 'method');
vi.useFakeTimers;

Главные отличия от Jest

1. ESM по умолчанию

// Vitest: import/export работает из коробки
import { formatPrice } from './utils';

test('форматирует цену', () => {
  expect(formatPrice(1999)).toBe('$19.99');
});

2. Watch mode по умолчанию

vitest        # watch mode (как jest --watch)
vitest run    # одноразовый запуск (как jest)

3. Скорость (Vite transform)

Проект 200 тестов:
Jest:   ~8 секунд
Vitest: ~2 секунды (первый запуск), ~0.5с (HMR)

4. Встроенный UI

npm install -D @vitest/ui
vitest --ui
# Открывается localhost:51204 с визуальным интерфейсом

5. In-source testing (тесты внутри исходника)

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// Тест прямо в файле! Удаляется при сборке.
if (import.meta.vitest) {
  const { test, expect } = import.meta.vitest;

  test('add', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
  });
}
// vite.config.ts — включение in-source testing
export default defineConfig({
  define: {
    'import.meta.vitest': 'undefined', // удаляется в production
  },
  test: {
    includeSource: ['src/**/*.ts'],
  },
});

Моки в Vitest

vi.fn

const mockFn = vi.fn;
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: 'ok' });
mockFn.mockImplementation((x) => x * 2);

vi.mock

vi.mock('./api', () => ({
  fetchUsers: vi.fn.mockResolvedValue(),
  fetchPosts: vi.fn.mockResolvedValue(),
}));

// Частичный мок
vi.mock('./utils', async () => {
  const actual = await vi.importActual('./utils');
  return {
    ...actual,
    formatDate: vi.fn.mockReturnValue('01.01.2025'),
  };
});

vi.spyOn

const spy = vi.spyOn(console, 'log');
doSomething;
expect(spy).toHaveBeenCalledWith('hello');
spy.mockRestore();

Fake timers

test('debounce', () => {
  vi.useFakeTimers;

  const fn = vi.fn;
  const debounced = debounce(fn, 300);

  debounced;
  debounced;
  debounced;

  vi.advanceTimersByTime(300);

  expect(fn).toHaveBeenCalledOnce;

  vi.useRealTimers;
});

Snapshot-тесты

test('snapshot', () => {
  const user = createUser('Алиса');
  expect(user).toMatchSnapshot;
});

// Inline snapshot
test('inline snapshot', () => {
  expect(formatDate(new Date('2025-01-01'))).toMatchInlineSnapshot('"01.01.2025"');
});

Type-safe тестирование

import { expectTypeOf } from 'vitest';

test('типы', () => {
  expectTypeOf(add).toBeFunction;
  expectTypeOf(add(1, 2)).toBeNumber;

  expectTypeOf<string>.toMatchTypeOf<string | number>;

  expectTypeOf({ name: 'test' }).toMatchTypeOf<{ name: string }>;
});

Миграция с Jest

Jest Vitest Изменение
jest.fn vi.fn Замена префикса
jest.mock vi.mock Замена префикса
jest.spyOn vi.spyOn Замена префикса
jest.useFakeTimers vi.useFakeTimers Замена префикса
jest.config.js vite.config.tstest Другой формат конфига
@jest/globals vitest Другой пакет для импортов

Автоматическая миграция

npx vitest --reporter=verbose --run
# Если globals: true — тесты работают без изменений
# Замени jest.* на vi.* — готово

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

1. globals: false и забытые импорты

// Если globals: false, нужен явный импорт
import { describe, test, expect, vi } from 'vitest';

2. vi.mock с ESM

// ПЛОХО: factory должна быть синхронной для top-level
vi.mock('./api', async () => {  // async может вызвать проблемы
  return { fetch: vi.fn };
});

// ХОРОШО: используй vi.importActual внутри
vi.mock('./api', async () => {
  const actual = await vi.importActual<typeof import('./api')>('./api');
  return { ...actual, fetch: vi.fn };
});

3. Не подключили environment для DOM

// Тест использует document, но environment: 'node'
// Ошибка: document is not defined
// Решение: environment: 'jsdom' или 'happy-dom'

Практика

  1. Настрой Vitest в существующем Vite-проекте (или создай новый npm create vite)
  2. Напиши тесты с vi.fn и vi.mock для API-сервиса
  3. Попробуй in-source testing: добавь тест внутри файла утилит
  4. Используй vitest --ui для визуального просмотра тестов
  5. Мигрируй один Jest-тестовый файл на Vitest (замени jest.* на vi.*)

Связанные темы

Ресурсы