Что пишут в блогах

Подписаться

Что пишут в блогах (EN)

Разделы портала

Онлайн-тренинги

.
Введение в тестирование с Vitest
20.10.2025 00:00

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

В последнее время я экспериментирую с Vitest — фреймворком для тестирования JavaScript- и TypeScript-приложений. Я начал работать с этим инструментом недавно и был приятно удивлён его возможностями.

Как правило, Vitest используют для юнит- и компонентного тестирования, но инструмент может гораздо больше. В этой статье мы рассмотрим его функции и варианты использования.

Начнем с основ

Если вы, как я, в основном работаете в end-to-end тестировании, то сразу заметите главное отличие подхода к тестированию у инструментов вроде Vitest. В e2e-тестировании вы запускаете приложение в браузере и проходите через определённый пользовательский сценарий. В Vitest вы фокусируетесь на конкретной части приложения. Это может быть компонент, функция или что-то ещё. Подход обычно заключается в том, чтобы импортировать этот фрагмент приложения в тест и проверять различные сценарии изолированно.

В качестве примера я буду использовать простое приложение страницы статуса, написанное на Next.js. Приложение отображает текущий статус системы и список предыдущих состояний.


В этом приложении у меня есть функция, которая возвращает цвет в зависимости от статуса сервиса.

statusData.ts

1  export function getStatusColor(status: Status): string {
2    switch (status) {
3      case "operational":
4        return "bg-green-500";
5      case "partial-outage":
6        return "bg-yellow-500";
7      case "major-outage":
8        return "bg-red-500";
9      default:
10        return "bg-gray-500";
11    }
12  }

Цель моего тестирования — убедиться, что функция возвращает правильный цвет для каждого статуса. Тест на Vitest для этой функции выглядел бы так

statusData.spec.ts

1  import { test, expect } from "vitest";
2  import { getStatusColor } from "./statusData";
3
4  test("getStatusColor returns the correct color for each status", () => {
5    expect(getStatusColor("operational")).toBe("bg-green-500");
6    expect(getStatusColor("partial-outage")).toBe("bg-yellow-500");
7    expect(getStatusColor("major-outage")).toBe("bg-red-500");
8    expect(getStatusColor("unknown")).toBe("bg-gray-500");
9  });

Тест просто проверяет все возможные для этой функции случаи и сравнивает возвращаемое значение с ожидаемым. Для такой элементарной функции тест выглядит довольно простым, но подобные тесты становятся особенно полезными, когда нам нужно работать с несколькими функциями, импортами или при рефакторинге существующего функционала.

Запуск тестов

Тут несколько способов запуска тестов. Самый простой — воспользоваться командной строкой:

1  npx vitest

Если вы используете VS Code или Cursor, у них есть расширение, которое позволяет запускать тесты прямо в редакторе кода. Также поддерживаются и другие редакторы.


Vitest также предлагает очень удобный UI-режим, в котором отображается дашборд всех ваших тестов. Его можно запустить следующей командой:

1  npx vitest --ui


UI-режим автоматически запускается в режиме watch — это значит, что при любом изменении теста или исходного файла тест будет перезапущен, и вы сразу получите обратную связь.

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

Так же, как функции, вы можете тестировать и компоненты. Давайте рассмотрим компонент, который использует эти цвета и содержит чуть больше логики. Вот пример компонента, который рендерит отдельный элемент списка. Важно отметить, что этот компонент принимает проп item. Позже мы проверим, что он правильно обрабатывается.

historical-status-item.tsx

1  import type { HistoricalStatus } from "../utils/statusData";
2  import { getStatusColor } from "../utils/statusData";
3
4  interface HistoricalStatusItemProps {
5    item: HistoricalStatus;
6  }
7
8  export function HistoricalStatusItem({ item }: HistoricalStatusItemProps) {
9    return (
10      <div className="flex items-center justify-between">
11        <span>{item.date}</span>
12        <div className="flex items-center space-x-2">
13          <span className="capitalize">{item.status.replace("-", " ")}</span>
14          <div className={`w-2 h-2 rounded-full ${getStatusColor(item.status)}`} />
15        </div>
16      </div>
17    );
18  }

Нам нужно сделать ещё пару шагов, чтобы протестировать наш компонент. Для этого его нужно отрендерить и правильно настроить. Мы установим несколько утилит, которые помогут с этим.

1  npm i @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom

Эти утилиты расширят возможности Vitest, добавят подходящую среду тестирования и помогут корректно настраивать компоненты для тестирования. Во-первых, нужно подключить плагин react в файл vitest.config.ts, а также задать тестовую среду.

vitest.config.ts

1  import { defineConfig } from 'vitest/config'
2  import react from '@vitejs/plugin-react'
3
4  export default defineConfig({
5    plugins: [react()],
6    test: {
7      environment: 'jsdom'
8    },
9  })

Теперь всё готово к написанию теста. Мы хотим отрендерить компонент <HistoricalStatusItem />. Как уже упоминалось, этот компонент требует передачи данных через пропсы. Мы можем просто создать мок-объект прямо в тесте, передать его в компонент и затем сделать необходимые проверки.

historical-status-item.spec.ts

1  import '@testing-library/jest-dom/vitest'
2  import { render, screen } from '@testing-library/react'
3  import { HistoricalStatusItem } from './historical-status-item'
4  import { expect, test } from 'vitest'
5
6  test('renders date and status correctly', () => {
7    const mockItem = {
8      date: '2023-05-30',
9      status: 'partial-outage' as const
10    }
11
12    render(<HistoricalStatusItem item={mockItem} />)
13
14    expect(screen.getByText('2023-05-30')).toBeInTheDocument()
15    expect(screen.getByText('partial outage')).toBeInTheDocument()
16  })

Когда мы создали несколько тестов компонентов, имеет смысл вынести повторяющийся код в отдельный файл. Типичный пример — импорт @testing-library/jest-dom, который будет нужен каждый раз, когда мы работаем с DOM-элементами. Этот импорт можно переместить в файл vitest.setup.ts и подключить его как файл настроек в конфигурации.

vitest.config.ts

1  import { defineConfig } from 'vitest/config'
2  import react from '@vitejs/plugin-react'
3
4  export default defineConfig({
5    plugins: [react()],
6    test: {
7      environment: 'jsdom',
8      setupFiles: ['./vitest.setup.ts'],
9    },
10  })

Компоненты с управляемым состоянием

Тестирование компонентов, использующих управление состоянием, может быть чуть сложнее. В этом приложении мы используем Zustand для управления состоянием. Компонент отображает статусы системы, полученные из хранилища, и показывает их в виде карточек.

current-status.tsx

1  "use client"
2
3  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
4  import { getStatusColor, getOverallStatus } from "@/utils/statusData"
5  import { useStatusStore } from "@/stores/statusStore"
6
7  export function CurrentStatus() {
8    const systems = useStatusStore((state) => state.systems)
9    const overallStatus = getOverallStatus(systems)
10    const statusColor = getStatusColor(overallStatus)
11
12    return (
13      <Card>
14        <CardHeader>
15          <CardTitle>Current Status</CardTitle>
16        </CardHeader>
17        <CardContent>
18          <div className="flex items-center space-x-2 mb-4">
19            <div className={`w-3 h-3 rounded-full ${statusColor}`} />
20            <span className="font-semibold capitalize">{overallStatus.replace("-", " ")}</span>
21          </div>
22          <div className="grid gap-2">
23            {systems.map((system) => (
24              <div key={system.name} className="flex items-center justify-between">
25                <span>{system.name}</span>
26                <div className={`w-2 h-2 rounded-full ${getStatusColor(system.status)}`} />
27              </div>
28            ))}
29          </div>
30        </CardContent>
31      </Card>
32    )
33  }

Чтобы протестировать этот компонент, нам нужно замокать хранилище. Мы не хотим полагаться на реальное управление состоянием в наших тестах. Во многих реальных сценариях доступ к состоянию может быть вовсе невозможен. Именно здесь возможности мокирования Vitest приходятся как нельзя кстати.

Мы можем использовать vi.mock(), чтобы перехватить импорт хранилища и подставить собственные данные. Мы просто передаём свои мок-данные туда, где компонент обычно получал бы данные из хранилища.

Обычно компонент получает данные с помощью хука useStatusStore, но в этом случае мы используем vi.fn(), чтобы «сказать» компоненту, что теперь хук useStatusStore возвращает именно эти данные.

Затем мы проверяем, что все мок-данные были корректно отрисованы в компоненте.

current-status.spec.ts

1  import { render, screen } from '@testing-library/react'
2  import { CurrentStatus } from './current-status'
3  import { expect, test, vi } from 'vitest'
4
5  test('renders all system statuses', () => {
6    vi.mock('store data', () => ({
7      useStatusStore: vi.fn((selector) => selector({ systems: [
8        { name: "API", status: "operational" },
9        { name: "Web App", status: "operational" },
10        { name: "Database", status: "operational" }
11      ]}))
12    }))
13
14    render(<CurrentStatus />)
15
16    expect(screen.getByText('API')).toBeInTheDocument()
17    expect(screen.getByText('Web App')).toBeInTheDocument()
18    expect(screen.getByText('Database')).toBeInTheDocument()
19  })

Это позволяет протестировать, как компонент рендерится и как он себя ведёт при различных состояниях системы, не настраивая всю инфраструктуру управления состоянием.

Режим браузера

У Vitest есть ещё одна интересная функция — browser mode. Сейчас она находится в экспериментальной стадии, но уже демонстрирует широкие возможности. Это поднимает тестирование компонентов на новый уровень, так как позволяет рендерить их прямо в браузере — в той самой среде, где они должны использоваться.

Вы даже можете взаимодействовать с компонентами с помощью Playwright или Webdriver.io. Установка довольно проста: CLI-утилита поможет вам пройти весь процесс, когда вы запустите команду npx vitest init browser. Это установит все необходимые зависимости и настроит конфигурационный файл за вас.

vitest.config.ts

1  import { defineConfig } from 'vitest/config'
2  import react from '@vitejs/plugin-react'
3  import { resolve } from 'path'
4
5  export default defineConfig({
6    plugins: [react()],
7    test: {
8      environment: 'jsdom',
9      setupFiles: ['./vitest.setup.ts'],
10      browser: {
11        enabled: true,
12        name: 'chromium',
13        provider: 'playwright'
14      }
15    }
16  })

Теперь наш тест требует небольшого обновления, чтобы компонент отображался внутри браузерной страницы. Мы просто находим базовый элемент с помощью page.elementLocator() и затем используем его для написания проверок компонента.

current-status.spec.ts

1  import { render } from '@testing-library/react'
2  import { CurrentStatus } from './current-status'
3  import { expect, test, vi } from 'vitest'
4  import { page } from '@vitest/browser/context'
5
6  test('renders all system statuses', async () => {
7    vi.mock('@/stores/statusStore', () => ({
8      useStatusStore: vi.fn((selector) => selector({ systems: [
9        { name: "API", status: "operational" },
10        { name: "Web App", status: "operational" },
11        { name: "Database", status: "operational" }
12      ]}))
13    }))
14
15    const { baseElement } = render(<CurrentStatus />)
16    const screen = page.elementLocator(baseElement)
17
18    await expect.element(screen.getByText('API')).toBeInTheDocument()
19    await expect.element(screen.getByText('Web App')).toBeInTheDocument()
20    await expect.element(screen.getByText('Database')).toBeInTheDocument()
21  })

Когда вы запустите Vitest в режиме UI, вы увидите новый раздел, где можно просматривать ваш компонент прямо в браузере.


При первом запуске вы можете не увидеть стили — в этом случае поможет обновление файла vitest.setup.ts, чтобы подключить CSS-файл напрямую.

vitest.setup.ts

1  import '@testing-library/jest-dom/vitest'
2  import '@/app/globals.css'

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

Лично я – большой фанат покрытия кода. Для меня это не погоня за процентами, а способ выявить «слепые зоны» в коде. Раньше я много работал с покрытием в Cypress.

Самое крутое в Vitest — то, что для покрытия кода не нужно специально модифицировать приложение. Vitest использует встроенное покрытие движка Chrome V8 для генерации отчётов.

Всё, что нужно сделать — это запустить тесты с флагом --coverage:

1  npx vitest --coverage

При первом запуске Vitest предложит установить пакет @vitest/coverage-v8. После выполнения тестов отчёт по покрытию будет сохранён в папке coverage, а также доступен прямо в UI-режиме Vitest.


Заключение

Я попробовал Vitest из любопытства — и был впечатлён, насколько легко он настраивается и насколько комфортен в работе. Особенно меня порадовал режим браузера — он работает невероятно быстро. То, что взаимодействие с компонентами реализовано через Playwright, даёт, на мой взгляд, огромный потенциал.

Компонентное тестирование и e2e стали ближе друг к другу. А в сочетании с покрытием кода, по-моему, мы получаем очень интересную и мощную тестовую связку.

Я поэкспериментирую с Vitest побольше и посмотрю на его дальнейшее развитие.

Обсудить в форуме