Unit тесты с Vitest

Vitest — это современный тест-раннер для проектов на основе Vite и SvelteKit. Его ключевое преимущество заключается в высокой скорости запуска, интеграции с Vite и поддержке современных возможностей JavaScript и TypeScript.

Для начала необходимо установить пакеты:

npm install -D vitest @testing-library/svelte @testing-library/jest-dom jsdom
  • vitest — основной тест-раннер.
  • @testing-library/svelte — библиотека для тестирования Svelte-компонентов через DOM.
  • @testing-library/jest-dom — расширение для удобных утверждений DOM.
  • jsdom — эмуляция браузерного окружения для Node.js.

Далее создается конфигурация в файле vitest.config.js:

import { defineConfig } from 'vitest/config';
import svelte from '@sveltejs/vite-plugin-svelte';

export default defineConfig({
  plugins: [svelte()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './vitest.setup.js',
  },
});

Файл vitest.setup.js подключает дополнительные возможности:

import '@testing-library/jest-dom';

Такое окружение позволяет писать тесты с использованием глобальных функций describe, it, expect.


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

SvelteKit компоненты тестируются как обычные UI-компоненты. Основной инструмент — render из @testing-library/svelte.

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

import { render, fireEvent } from '@testing-library/svelte';
import Button from '../src/lib/Button.svelte';

describe('Button Component', () => {
  it('рендерит текст', () => {
    const { getByText } = render(Button, { props: { label: 'Нажми меня' } });
    expect(getByText('Нажми меня')).toBeInTheDocument();
  });

  it('вызывает callback при клике', async () => {
    const handleClick = vi.fn();
    const { getByText } = render(Button, { props: { label: 'Клик', onClick: handleClick } });

    const button = getByText('Клик');
    await fireEvent.click(button);

    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Ключевые моменты:

  • render(Component, { props }) — рендерит компонент с указанными свойствами.
  • fireEvent — эмуляция пользовательских действий (клик, ввод текста).
  • vi.fn() — создание мок-функций для проверки вызовов.

Использование mock и stub функций

Vitest предоставляет API для моков и заглушек, аналогичное Jest:

import { vi } from 'vitest';
import * as api from '../src/lib/api.js';

vi.mock('../src/lib/api.js', () => ({
  fetchData: vi.fn(() => Promise.resolve({ data: [1, 2, 3] })),
}));

it('должен получить данные', async () => {
  const result = await api.fetchData();
  expect(result.data).toHaveLength(3);
});

Моки полезны для изоляции тестов от сетевых запросов или сторонних библиотек.


Тестирование реактивности Svelte

Svelte-компоненты имеют реактивные переменные, которые можно проверять через DOM или подписки:

import { render } from '@testing-library/svelte';
import Counter from '../src/lib/Counter.svelte';

it('увеличивает счетчик', async () => {
  const { getByText } = render(Counter);
  const incrementButton = getByText('Increment');

  await fireEvent.click(incrementButton);
  expect(getByText('Count: 1')).toBeInTheDocument();

  await fireEvent.click(incrementButton);
  expect(getByText('Count: 2')).toBeInTheDocument();
});

Тест проверяет правильное обновление значения счетчика через пользовательские действия.

Для реактивных выражений можно подписываться на store:

import { writable } from 'svelte/store';

it('store изменяется корректно', () => {
  const count = writable(0);
  let value;
  count.subscribe(v => value = v);

  count.set(5);
  expect(value).toBe(5);
});

Организация тестов и структура файлов

Для SvelteKit принято хранить тесты рядом с компонентами:

src/
 └─ lib/
     ├─ Button.svelte
     ├─ Button.test.js
     ├─ Counter.svelte
     └─ Counter.test.js

Или в отдельной папке tests:

tests/
 ├─ components/
 │   ├─ Button.test.js
 │   └─ Counter.test.js
 └─ utils/
     └─ api.test.js

Называние файлов с .test.js или .spec.js позволяет Vitest автоматически их обнаруживать.


Тестирование событий и пользовательских действий

Для UI-компонентов критически важно проверять интерактивность:

  • Клики: fireEvent.click(element)
  • Ввод текста: fireEvent.input(element, { target: { value: 'text' } })
  • Изменение формы: fireEvent.change(element, { target: { value: 'new' } })

Можно комбинировать события для сложных сценариев:

await fireEvent.input(inputElement, { target: { value: 'Svelte' } });
await fireEvent.click(submitButton);

expect(handleSubmit).toHaveBeenCalledWith('Svelte');

Использование асинхронных тестов

Асинхронные операции в компонентах обрабатываются через async/await:

import AsyncComponent from '../src/lib/AsyncComponent.svelte';

it('отображает данные после загрузки', async () => {
  const { findByText } = render(AsyncComponent);

  const item = await findByText('Loaded Item');
  expect(item).toBeInTheDocument();
});
  • findByText автоматически ждет появления элемента в DOM.
  • Позволяет тестировать компоненты с fetch-запросами, таймерами и эффектами.

Настройка глобальных утилит

Vitest позволяет добавлять глобальные хелперы через setupFiles:

import { render } from '@testing-library/svelte';
global.renderComponent = render;

Теперь renderComponent доступен во всех тестах без импорта. Это ускоряет написание и поддержание тестов.


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

Vitest запускает тесты параллельно по файлам. Для изоляции состояния:

  • Использовать beforeEach для сброса mocks и store.
  • afterEach для очистки DOM:
import { cleanup } from '@testing-library/svelte';

afterEach(() => {
  cleanup();
  vi.clearAllMocks();
});

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

Vitest легко интегрируется с GitHub Actions, GitLab CI и другими сервисами:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 20
      - run: npm install
      - run: npm test
  • Использование --coverage позволяет получать отчет о покрытии тестами.
  • Vitest поддерживает формат lcov, что удобно для анализа в CI.

Расширенные возможности Vitest

  • Snapshot-тестирование: проверка структуры компонентов.
  • Coverage: анализ покрытия кода тестами.
  • Mocking модулей и таймеров: для имитации setTimeout и fetch.
  • Совместимость с TypeScript: полная поддержка типизации в тестах.

Эти возможности делают Vitest мощным инструментом для unit-тестирования SvelteKit UI-компонентов, обеспечивая надежность и предсказуемость поведения интерфейса.