12k
All articles

Тестирование компонентов Svelte 5 с помощью Vitest

Тестируйте компоненты Svelte 5 с Vitest через mount, testing-library или browser mode, а также с правильной настройкой runes, эффектов и snippets.

OpenReplay Team
OpenReplay Team
Тестирование компонентов Svelte 5 с помощью Vitest

В Svelte 5 изменился подход к настройке тестов компонентов: конструктор new Component({ target }) был удалён. Вместо него используйте монтирование через mount() из svelte, render() из @testing-library/svelte или render() из vitest-browser-svelte. Рюны ($state, $derived, $effect, $props) работают только после обработки файла компилятором Svelte, поэтому конфигурация тестов должна направлять тестовые файлы через этот компилятор. В данном руководстве рассматриваются актуальная конфигурация Vitest и конкретные паттерны для тестирования рюн и компонентов в Svelte 5.

Ключевые выводы

  • В Svelte 5 удалены конструктор new Component({ target }), а также методы $set, $on и $destroy; используйте монтирование через mount() из svelte или вспомогательную функцию render(), а для чтения пропсов применяйте $props().
  • Для прямого тестирования рюн помещайте их в файл, имя которого содержит .svelte (например, counter.svelte.test.ts), чтобы компилятор обработал рюны до запуска утверждений Vitest.
  • Эффекты не выполняются синхронно — оборачивайте код, использующий $effect, в $effect.root() и вызывайте flushSync() для принудительного выполнения отложенных эффектов перед утверждениями.
  • Для работы @testing-library/svelte со Svelte 5 добавьте плагин svelteTesting из @testing-library/svelte/vite; он устанавливает условие разрешения browser и автоматически очищает DOM после каждого теста.
  • vitest-browser-svelte запускает компонент в реальном браузере через Playwright и требует Vitest 4; всегда используйте await render(...), запрашивайте элементы через локаторы и выполняйте утверждения с помощью await expect.element(...).

Что изменилось в тестировании в Svelte 5?

Большинство руководств по тестированию Svelte в интернете написаны для эпохи Svelte 4 и используют API, которые больше не существуют. Если в руководстве компонент создаётся через new, вызывается component.$set или используется $$props — оно устарело. Ниже приведена таблица миграции:

Svelte 4 (удалено)Svelte 5 (актуально)
new Component({ target })mount(Component, { target }) или render(Component)
component.$set(props)передача пропсов в render / rerender
component.$on / component.$destroyколбэк-пропсы / unmount(component)
$$props$props()
подход с fireEventuserEvent или локаторы в browser-режиме
настройка через svelte-jesterплагин svelteTesting (Vitest)

Существует два актуальных подхода, и оба являются корректными. Первый — @testing-library/svelte на базе jsdom: высокоуровневый, знакомый и поддерживающий Svelte версий 3, 4 и 5. Второй — vitest-browser-svelte, который рендерит компоненты в реальном браузере через Playwright, используя стабильный Browser Mode Vitest. Browser Mode утратил статус экспериментального в Vitest 4, поэтому игнорируйте руководства, в которых он по-прежнему называется экспериментальным.

Как настроить Vitest для тестирования Svelte?

Любая конфигурация тестов Svelte 5 имеет одно общее требование: Vitest должен разрешать точки входа browser ваших пакетов, даже несмотря на то, что работает в Node. В документации Svelte это реализуется через resolve.conditions. Начните с базовой конфигурации и расширяйте её по мере необходимости.

Для тестирования компонентов на jsdom установите jsdom и добавьте окружение и плагин svelteTesting:

// vite.config.js
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { svelteTesting } from '@testing-library/svelte/vite';

export default defineConfig({
  plugins: [svelte(), svelteTesting()],
  test: {
    environment: 'jsdom'
  }
});

Плагин svelteTesting самостоятельно устанавливает условие разрешения browser и в Vitest автоматически выполняет инициализацию и очистку DOM до и после каждого теста — вам не нужно вручную писать afterEach(cleanup). Не добавляйте resolve.conditions вручную: плагин уже берёт это на себя.

Для тестирования в реальном браузере используйте Browser Mode Vitest. Начиная с Vitest 4, пакеты провайдеров устанавливаются отдельно, а конфигурация импортирует playwright() из @vitest/browser-playwright с массивом instances — устаревшая форма provider: 'playwright', name: 'chromium' более не поддерживается:

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

export default defineConfig({
  plugins: [svelte()],
  test: {
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [{ browser: 'chromium' }]
    }
  }
});

Написание теста компонента

Компонент Svelte 5 получает входные данные через $props() и хранит локальное состояние в $state. Вот компонент, который будут тестировать оба подхода:

<!-- Counter.svelte -->
<script>
  let { initial = 0 } = $props();
  let count = $state(initial);
</script>

<button onclick={() => count++}>{count}</button>

С @testing-library/svelte вызовите render, запросите элемент по роли, взаимодействуйте с ним через userEvent и используйте await для клика:

import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments on click', async () => {
  const user = userEvent.setup();
  render(Counter, { initial: 0 });
  const button = screen.getByRole('button');
  expect(button).toHaveTextContent('0');
  await user.click(button);
  expect(button).toHaveTextContent('1');
});

mount()/unmount() из Svelte — это низкоуровневый API, лежащий в основе этих вспомогательных функций. В документации отмечается, что прямое использование mount() является «низкоуровневым и несколько хрупким», поскольку предполагает утверждения против точного содержимого innerHTML, — поэтому для тестирования компонентов предпочтительнее использовать вспомогательные функции рендеринга.

С vitest-browser-svelte всегда используйте await render(...), запрашивайте элементы через локаторы и выполняйте утверждения с помощью expect.element, который автоматически повторяет попытки до успешного выполнения утверждения:

import { render } from 'vitest-browser-svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments on click', async () => {
  const screen = await render(Counter, { initial: 0 });
  const button = screen.getByRole('button');
  await button.click();
  await expect.element(button).toHaveTextContent('1');
});

Тестирование рюн и реактивной логики

Прежде чем монтировать что-либо, задайтесь вопросом: действительно ли вам нужен тест компонента? Документация Svelte рекомендует выносить реактивную логику в модуль .svelte.js и тестировать её изолированно, без накладных расходов на компонент. Такой модуль может использовать рюны, поскольку его имя содержит .svelte:

// counter.svelte.js
export function createCounter(initial = 0) {
  let count = $state(initial);
  const doubled = $derived(count * 2);
  return {
    get count() { return count; },
    get doubled() { return doubled; },
    increment() { count++; }
  };
}

Тестируйте его напрямую — тестовый файл также должен содержать .svelte в имени, например counter.svelte.test.js, чтобы компилятор обработал рюны перед запуском утверждений Vitest:

import { expect, test } from 'vitest';
import { createCounter } from './counter.svelte.js';

test('derives doubled from count', () => {
  const counter = createCounter(2);
  expect(counter.doubled).toBe(4);
  counter.increment();
  expect(counter.doubled).toBe(6);
});

Эффекты являются исключением: они не выполняются синхронно. Когда тестируемый код использует $effect, оберните его в $effect.root() и вызовите flushSync() для выполнения отложенных эффектов перед утверждениями — именно так показано в документации Svelte по тестированию:

import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { logger } from './logger.svelte.js';

test('logs each update', () => {
  const cleanup = $effect.root(() => {
    let count = $state(0);
    const log = logger(() => count);
    flushSync();
    expect(log).toEqual([0]);
    count = 1;
    flushSync();
    expect(log).toEqual([0, 1]);
  });
  cleanup();
});

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

Сниппеты в Svelte 5 заменяют слоты: они рендерятся через {@render} и принимаются через $props(). Для компонента, рендерящего сниппет children, простейший тест — небольшой компонент-обёртка с атрибутом data-testid, по которому затем выполняется запрос. Для сниппетов, аргументы которых нужно проверить, в документации vitest-browser-svelte используется API createRawSnippet из Svelte: сниппет передаётся напрямую, и проверяется, что он получил:

<!-- Greeting.svelte -->
<script>
  let { name, message } = $props();
  const greeting = $derived(`Hello, ${name}!`);
</script>

<p>{@render message?.(greeting)}</p>
import { render } from 'vitest-browser-svelte';
import { createRawSnippet } from 'svelte';
import { expect, test } from 'vitest';
import Greeting from './Greeting.svelte';

test('passes the greeting into the snippet', async () => {
  const screen = await render(Greeting, {
    name: 'Alice',
    message: createRawSnippet((greeting) => ({
      render: () => `<span data-testid="message">${greeting()}</span>`
    }))
  });
  await expect.element(screen.getByTestId('message'))
    .toHaveTextContent('Hello, Alice!');
});

jsdom или browser mode: что выбрать?

Выбирайте jsdom + @testing-library/svelte для быстрых тестов разметки и логики без запуска браузера; выбирайте vitest-browser-svelte, когда вам нужны реальные браузерные API — работа с макетом, фокусом, IntersectionObserver — без необходимости их имитировать.

jsdom + testing-libraryvitest-browser-svelte
ОкружениеСимулированный DOM (jsdom)Реальный браузер через Playwright
Скорость / настройкаБыстро, без загрузки браузераБолее высокие накладные расходы на тест; требуется браузер
Браузерные APIИмитируются / мокируютсяНативные, без имитации
Синхронный сбросЧасто требуется flushSyncЛокаторы повторяют попытки автоматически; редко нужен
ТребованияПоддержка Svelte 3/4/5Vitest 4

Поскольку локаторы в browser-режиме и expect.element повторяют попытки до успешного выполнения утверждения, в тестах компонентов там редко требуется flushSync — хотя в отдельных граничных случаях он всё же нужен. Держите чистую реактивную логику в файлах .svelte.test на jsdom ради скорости, а browser-режим резервируйте для поведения, зависящего от настоящего движка рендеринга.

Начните с выноса логики в модули .svelte.js и тестирования её изолированно, добавьте тесты компонентов на jsdom через плагин svelteTesting и прибегайте к vitest-browser-svelte только тогда, когда тест действительно требует реального браузера. Настройте конфигурацию один раз, зафиксируйте актуальные API из этого руководства — и ваш набор тестов Svelte 5 будет защищён от устаревших паттернов Svelte 4, которые ломают большинство старых руководств.

Часто задаваемые вопросы

Почему мой $effect не выполняется в тесте Vitest?

Эффекты не выполняются синхронно в тестах, поэтому утверждение, размещённое сразу после изменения состояния, видит устаревшие значения. Оберните код, использующий эффект, в $effect.root() и вызовите flushSync() из svelte для принудительного выполнения отложенных эффектов перед утверждениями. По завершении теста вызовите функцию очистки, возвращённую $effect.root(). В тестах в browser-режиме это редко требуется, поскольку локаторы и expect.element повторяют попытки автоматически, хотя в отдельных граничных случаях flushSync всё же необходим.

Нужен ли мне svelte-jester для тестирования компонентов Svelte 5?

Нет — svelte-jester предназначен исключительно для Jest и не нужен при использовании Vitest. Для Vitest добавьте плагин svelteTesting из @testing-library/svelte/vite: он устанавливает условие разрешения browser и автоматически очищает DOM после каждого теста. svelte-jester по-прежнему упоминается в документации testing-library как запасной вариант для Jest, но если вы используете Vitest — его следует игнорировать. Многие устаревшие руководства копируют путь настройки для Jest, что приводит к излишним сбоям конфигурации.

Можно ли тестировать рюны Svelte 5 в обычном файле .test.js?

Нет. Рюны работают только после обработки файла компилятором Svelte, а компилятор обрабатывает лишь файлы, имя которых содержит .svelte. Для прямого тестирования рюн назовите файл с .svelte в имени, например counter.svelte.test.js, чтобы компилятор преобразовал рюны до запуска утверждений Vitest. То же правило применяется к обычным модулям, использующим рюны: называйте их с .svelte, например counter.svelte.js, и импортируйте их в тесты обычным образом.

Какая версия Vitest требуется для vitest-browser-svelte?

vitest-browser-svelte требует Vitest версии 4.0.0 или выше; установка с Vitest 3 или более ранней версией завершится ошибкой. Browser Mode стал стабильным в Vitest 4, который также перенёс пакеты провайдеров в отдельные установки — вы импортируете playwright() из @vitest/browser-playwright и настраиваете массив instances. Устаревшая форма provider: 'playwright', name: 'chromium' из Vitest 2 более не поддерживается и является некорректной. Пакет размещён в организации vitest-community на GitHub.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.