Vitest как нативный тест-раннер для Vite

Vitest представляет собой современный тестовый фреймворк, разработанный специально для тесной интеграции с Vite. В отличие от классических решений вроде Jest, Vitest использует инфраструктуру Vite напрямую: систему трансформации модулей, граф зависимостей, плагины и механизм ESM.

Основная идея Vitest заключается в устранении промежуточных слоёв между приложением и тестовой средой. Благодаря этому достигаются:

  • высокая скорость запуска;
  • мгновенный Hot Reload тестов;
  • нативная поддержка ESM;
  • единая конфигурация с Vite;
  • минимальное дублирование настроек;
  • быстрый кеш трансформаций.

Vitest особенно эффективен в проектах на:

  • Vue;
  • React;
  • Svelte;
  • Solid;
  • Vanilla JS;
  • TypeScript.

Установка Vitest

Базовая установка выполняется через npm:

npm install -D vitest

Для работы с DOM обычно дополнительно подключаются:

npm install -D jsdom @testing-library/dom

Для React:

npm install -D @testing-library/react

Для Vue:

npm install -D @vue/test-utils

Минимальная конфигурация

Vitest может использовать существующий vite.config.js, однако тестовые параметры чаще выносятся в отдельную секцию.

Пример:

// vite.config.js
import { defineConfig } from 'vite'

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

Параметр globals позволяет использовать глобальные функции:

describe()
it()
expect()
beforeEach()

Без необходимости импортировать их вручную.


Создание первого теста

Пример простой функции:

// math.js
export function sum(a, b) {
  return a + b
}

Тест:

// math.test.js
import { sum } from './math'

describe('sum', () => {
  it('adds numbers', () => {
    expect(sum(2, 3)).toBe(5)
  })
})

Запуск:

npx vitest

Режим наблюдения:

npx vitest --watch

Архитектура работы Vitest

Vitest использует внутренние механизмы Vite:

  1. Модуль импортируется через ESM.
  2. Vite трансформирует код.
  3. Применяются плагины.
  4. Выполняется кеширование.
  5. Код исполняется внутри тестового раннера.

За счёт этого тесты используют ту же цепочку обработки, что и основное приложение.

Это устраняет типичную проблему рассинхронизации между:

  • production-сборкой;
  • dev-сервером;
  • тестовой средой.

Поддержка ESM

Vitest ориентирован на современный JavaScript и нативные ES-модули.

Пример:

import { userService } from './services/userService.js'

В отличие от старых тестовых раннеров, не требуется:

  • Babel-конфигурация;
  • CommonJS-обёртки;
  • transpile-плагины;
  • отдельная настройка модулей.

Работа с TypeScript

Vitest автоматически использует TypeScript через Vite.

Файл:

// calculator.ts
export function multiply(a: number, b: number): number {
  return a * b
}

Тест:

import { multiply } from './calculator'

describe('multiply', () => {
  it('multiplies values', () => {
    expect(multiply(4, 5)).toBe(20)
  })
})

Отдельная настройка TypeScript-компилятора обычно не требуется.


Глобальные API

Vitest предоставляет familiar API, схожий с Jest.

Основные функции:

API Назначение
describe группировка тестов
it/test отдельный тест
expect проверки
beforeEach код перед тестом
afterEach очистка
beforeAll подготовка
afterAll завершение

Assertions через expect

Проверка примитивов

expect(value).toBe(10)

Глубокое сравнение

expect(user).toEqual({
  id: 1,
  name: 'Alex'
})

Проверка truthy/falsy

expect(data).toBeTruthy()
expect(error).toBeFalsy()

Проверка массивов

expect(items).toContain('admin')

Проверка ошибок

expect(() => {
  throw new Error('Fail')
}).toThrow()

Асинхронное тестирование

Async/await

it('loads data', async () => {
  const result = await fetchUsers()

  expect(result.length).toBe(3)
})

Проверка reject

await expect(loadUser()).rejects.toThrow()

Проверка resolve

await expect(getStatus()).resolves.toBe('ok')

Моки в Vitest

Vitest предоставляет встроенную систему мокирования через vi.


Создание mock-функции

const fn = vi.fn()

fn('hello')

expect(fn).toHaveBeenCalled()

Проверка аргументов

expect(fn).toHaveBeenCalledWith('hello')

Возврат значений

const fn = vi.fn(() => 42)

expect(fn()).toBe(42)

Mock модулей

Пример:

vi.mock('./api', () => {
  return {
    getUsers: vi.fn(() => [])
  }
})

Vitest перехватывает импорт модуля и подменяет его содержимое.


Spy-функции

Spy позволяет отслеживать вызовы существующих методов.

const spy = vi.spyOn(console, 'log')

console.log('test')

expect(spy).toHaveBeenCalled()

Восстановление:

spy.mockRestore()

Fake timers

Тестирование таймеров:

vi.useFakeTimers()

const callback = vi.fn()

setTimeout(callback, 1000)

vi.advanceTimersByTime(1000)

expect(callback).toHaveBeenCalled()

Тестирование DOM

Vitest может работать с браузерным окружением через jsdom.

Конфигурация:

test: {
  environment: 'jsdom'
}

Пример:

document.body.innerHTML = `
  <button id="btn">Click</button>
`

const button = document.querySelector('#btn')

expect(button.textContent).toBe('Click')

Testing Library

Часто Vitest используется вместе с Testing Library.

Пример React-компонента:

function Counter() {
  return <button>Increment</button>
}

Тест:

import { render, screen } from '@testing-library/react'

it('renders button', () => {
  render(<Counter />)

  expect(screen.getByText('Increment')).toBeDefined()
})

Snapshot-тестирование

Vitest поддерживает snapshots.

expect(data).toMatchSnapshot()

При первом запуске создаётся snapshot-файл.

Повторные запуски сравнивают текущее состояние со снимком.


Inline snapshots

expect(user).toMatchInlineSnapshot(`
  {
    "id": 1,
    "name": "Alex",
  }
`)

Покрытие кода

Vitest поддерживает coverage через V8.

Установка:

npm install -D @vitest/coverage-v8

Конфигурация:

test: {
  coverage: {
    provider: 'v8'
  }
}

Запуск:

npx vitest run --coverage

Изоляция тестов

Каждый тест должен быть независимым.

Пример очистки:

beforeEach(() => {
  localStorage.clear()
})

Изоляция предотвращает:

  • утечки состояния;
  • нестабильные тесты;
  • случайные зависимости;
  • ошибки порядка выполнения.

Параллельное выполнение

Vitest поддерживает многопоточность.

Тесты автоматически распределяются между worker-процессами.

Это особенно важно для:

  • крупных frontend-проектов;
  • monorepo;
  • CI/CD;
  • больших наборов unit-тестов.

Последовательное выполнение

Иногда требуется отключить параллелизм:

test: {
  sequence: {
    concurrent: false
  }
}

Фильтрация тестов

Запуск конкретного файла:

npx vitest math.test.js

Запуск по имени:

npx vitest -t "adds numbers"

only и skip

Выполнение одного теста

it.only('works', () => {

})

Пропуск

it.skip('temporary disabled', () => {

})

UI-режим Vitest

Vitest имеет встроенный интерфейс:

npx vitest --ui

Требуется установка:

npm install -D @vitest/ui

Интерфейс показывает:

  • статус тестов;
  • ошибки;
  • стек вызовов;
  • snapshots;
  • длительность;
  • дерево тестов.

Browser Mode

Vitest поддерживает запуск тестов в реальном браузере.

Пример:

test: {
  browser: {
    enabled: true,
    name: 'chrome'
  }
}

Это полезно для:

  • проверки реального DOM;
  • browser API;
  • canvas;
  • layout;
  • Web Components.

Интеграция с CI/CD

Запуск в CI:

npx vitest run

Обычно используется вместе с:

  • GitHub Actions;
  • GitLab CI;
  • Jenkins;
  • TeamCity.

Пример GitHub Actions

name: tests

on:
  push:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm install

      - run: npx vitest run

Интеграция с Vue

Vitest особенно тесно связан с Vue-экосистемой.

Пример:

<script setup>
defineProps({
  title: String
})
</script>

<template>
  <h1>{{ title }}</h1>
</template>

Тест:

import { mount } from '@vue/test-utils'
import Component from './Component.vue'

it('renders title', () => {
  const wrapper = mount(Component, {
    props: {
      title: 'Hello'
    }
  })

  expect(wrapper.text()).toContain('Hello')
})

Интеграция с React

Пример React-хука:

import { useState } from 'react'

export function useCounter() {
  const [count, setCount] = useState(0)

  return {
    count,
    increment: () => setCount(v => v + 1)
  }
}

Тестирование:

import { renderHook, act } from '@testing-library/react'

it('increments counter', () => {
  const { result } = renderHook(() => useCounter())

  act(() => {
    result.current.increment()
  })

  expect(result.current.count).toBe(1)
})

Настройка alias

Vitest использует alias из Vite.

resolve: {
  alias: {
    '@': '/src'
  }
}

Тест:

import { api } from '@/services/api'

Дополнительная настройка не требуется.


Setup-файлы

Общие настройки:

test: {
  setupFiles: './tests/setup.js'
}

Пример:

import '@testing-library/jest-dom'

Очистка моков

Автоматическая очистка:

test: {
  clearMocks: true,
  restoreMocks: true
}

Это снижает вероятность утечки состояния между тестами.


Bench API

Vitest поддерживает benchmarking.

Пример:

import { bench } from 'vitest'

bench('sort array', () => {
  ;[...Array(1000)]
    .map(() => Math.random())
    .sort()
})

Отличия Vitest от Jest

Vitest Jest
Нативный ESM CommonJS-истоки
Интеграция с Vite Отдельная система
Быстрый cold start Более тяжёлый запуск
Использует Vite pipeline Собственный pipeline
Современная архитектура Legacy-подходы
Отличная поддержка TS Требует больше конфигурации

Производительность Vitest

Высокая скорость достигается благодаря:

  • кешированию Vite;
  • ESM;
  • lazy-transform;
  • worker threads;
  • повторному использованию графа модулей.

На крупных проектах разница между Jest и Vitest может быть весьма заметной.


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

Распространённый вариант:

src/
tests/

Либо co-location:

Button.jsx
Button.test.jsx

Практика именования

Хорошие названия:

it('returns empty array for guests')

Плохие:

it('test 1')

Название должно описывать поведение, а не реализацию.


Unit и Integration тесты

Vitest подходит для:

Unit Testing

Изолированная проверка функций.

Integration Testing

Проверка взаимодействия модулей.

Component Testing

Тестирование UI-компонентов.


Ограничения Vitest

Несмотря на преимущества, существуют особенности:

  • некоторые старые Jest-плагины несовместимы;
  • часть legacy-инфраструктуры ориентирована на CommonJS;
  • Browser Mode всё ещё развивается;
  • специфические enterprise-решения могут требовать адаптации.

Монорепозитории

Vitest хорошо работает с monorepo:

packages/
  core/
  ui/
  api/

Каждый пакет может иметь собственную конфигурацию либо использовать общую базовую.


Конфигурация через defineConfig

Рекомендуемый подход:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true
  }
})

Использование vitest/config улучшает типизацию и автодополнение.


Debugging тестов

Запуск:

npx vitest --inspect

Позволяет подключать:

  • Chrome DevTools;
  • VSCode debugger;
  • Node Inspector.

Hot Reload тестов

Одно из ключевых преимуществ Vitest — мгновенное обновление тестов при изменении файлов.

При сохранении:

  1. Vite определяет изменённые модули.
  2. Перестраивается граф зависимостей.
  3. Перезапускаются только затронутые тесты.

Это существенно ускоряет TDD-процесс.


Использование в больших frontend-проектах

Vitest особенно эффективен в проектах с:

  • большим количеством компонентов;
  • сложной системой alias;
  • TypeScript;
  • SSR;
  • Vite-плагинами;
  • активным HMR.

Единая инфраструктура разработки и тестирования снижает сложность поддержки проекта и уменьшает объём конфигурации.