12k
All articles

Tester les composants Svelte 5 avec Vitest

Testez les composants Svelte 5 avec Vitest via mount, testing-library ou le mode navigateur, avec la bonne config pour runes, effets et snippets.

OpenReplay Team
OpenReplay Team
Tester les composants Svelte 5 avec Vitest

Svelte 5 a modifié la façon dont vous configurez les tests de composants : il n’est plus possible d’instancier un composant avec new Component({ target }) — cette API constructeur a été supprimée. À la place, montez les composants avec mount() de svelte, render() de @testing-library/svelte, ou render() de vitest-browser-svelte. Les runes ($state, $derived, $effect, $props) ne s’exécutent qu’une fois que le compilateur Svelte a traité le fichier, donc votre configuration de test doit faire passer les fichiers de test par ce compilateur. Ce guide couvre la configuration Vitest actuelle et correcte, ainsi que les patterns spécifiques pour tester les runes et les composants dans Svelte 5.

Points clés à retenir

  • Dans Svelte 5, le constructeur new Component({ target }) ainsi que $set, $on et $destroy ont été supprimés ; montez les composants avec mount() de svelte ou un helper render(), et accédez aux props via $props().
  • Pour tester les runes directement, placez-les dans un fichier dont le nom contient .svelte (par exemple counter.svelte.test.ts) afin que le compilateur traite les runes avant que Vitest n’exécute les assertions.
  • Les effets ne s’exécutent pas de manière synchrone — encapsulez le code utilisant $effect dans $effect.root() et appelez flushSync() pour vider les effets en attente avant d’effectuer vos assertions.
  • Pour @testing-library/svelte avec Svelte 5, ajoutez le plugin svelteTesting de @testing-library/svelte/vite ; il définit la condition de résolution browser et nettoie automatiquement le DOM après chaque test.
  • vitest-browser-svelte exécute votre composant dans un vrai navigateur via Playwright et nécessite Vitest 4 ; utilisez toujours await render(...), interrogez avec des locators, et effectuez vos assertions avec await expect.element(...).

Qu’est-ce qui a changé pour les tests dans Svelte 5 ?

La plupart des tutoriels de test Svelte disponibles en ligne datent de l’ère Svelte 4 et utilisent des API qui n’existent plus. Si un guide instancie un composant avec new, appelle component.$set, ou lit $$props, il est obsolète. Voici la table de migration :

Svelte 4 (supprimé)Svelte 5 (actuel)
new Component({ target })mount(Component, { target }) ou render(Component)
component.$set(props)passer les props à render / rerender
component.$on / component.$destroyprops de callback / unmount(component)
$$props$props()
Priorité à fireEventuserEvent ou locators en mode navigateur
Configuration svelte-jesterplugin svelteTesting (Vitest)

Deux configurations sont actuellement valides. La première utilise @testing-library/svelte avec jsdom — de haut niveau, familière, et compatible avec les versions 3, 4 et 5 de Svelte. La seconde est vitest-browser-svelte, qui rend les composants dans un vrai navigateur via Playwright en utilisant le Browser Mode stable de Vitest. Le Browser Mode a perdu son statut expérimental dans Vitest 4, donc ignorez tout tutoriel qui le qualifie encore d’expérimental.

Comment configurer Vitest pour les tests Svelte ?

Toute configuration de test Svelte 5 partage une exigence commune : Vitest doit résoudre les points d’entrée browser de vos packages même s’il s’exécute dans Node. La documentation Svelte réalise cela avec resolve.conditions. Commencez par une configuration de base et enrichissez-la progressivement.

Pour les tests de composants sur jsdom, installez jsdom et ajoutez l’environnement ainsi que le plugin 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'
  }
});

Le plugin svelteTesting définit automatiquement la condition de résolution browser et, dans Vitest, initialise et nettoie le DOM avant et après chaque test — vous n’avez donc pas besoin d’écrire manuellement afterEach(cleanup). N’écrivez pas également resolve.conditions à la main ; le plugin s’en charge.

Pour les tests en vrai navigateur, activez le Browser Mode de Vitest. Depuis Vitest 4, les packages de providers s’installent séparément, et la configuration importe playwright() depuis @vitest/browser-playwright avec un tableau instances — l’ancienne forme provider: 'playwright', name: 'chromium' est dépréciée :

// 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' }]
    }
  }
});

Écrire un test de composant

Un composant Svelte 5 lit ses entrées avec $props() et maintient son état local avec $state. Voici le composant que les deux approches vont tester :

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

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

Avec @testing-library/svelte, appelez render, interrogez par rôle, simulez les interactions avec userEvent, et utilisez await pour le clic :

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');
});

Le mount()/unmount() natif de Svelte est l’API bas niveau qui sous-tend ces helpers. La documentation note que l’approche avec mount() brut est « bas niveau et quelque peu fragile » car elle effectue des assertions sur le innerHTML exact ; préférez donc un helper de rendu pour les tests de composants.

Avec vitest-browser-svelte, utilisez toujours await render(...), interrogez avec des locators, et effectuez vos assertions avec expect.element, qui réessaie automatiquement jusqu’à ce que l’assertion réussisse :

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');
});

Tester les runes et la logique réactive

Avant de monter quoi que ce soit, demandez-vous si vous avez réellement besoin d’un test de composant. La documentation Svelte recommande d’extraire la logique réactive dans un module .svelte.js et de la tester de manière isolée, sans la surcharge d’un composant. Ce module peut utiliser des runes car son nom de fichier contient .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++; }
  };
}

Testez-le directement — le fichier de test lui-même doit également contenir .svelte dans son nom, par exemple counter.svelte.test.js, afin que le compilateur traite les runes :

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);
});

Les effets constituent l’exception : ils ne s’exécutent pas de manière synchrone. Lorsque le code testé utilise $effect, encapsulez-le dans $effect.root() et appelez flushSync() pour exécuter les effets en attente avant d’effectuer vos assertions, exactement comme le montrent la documentation de test 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();
});

Tester les snippets et les props

Les snippets sont le remplacement Svelte 5 des slots, rendus avec {@render} et reçus via $props(). Pour un composant qui rend un snippet children, le test le plus simple consiste à utiliser un petit composant wrapper avec un data-testid, puis à l’interroger. Pour les snippets dont vous souhaitez inspecter les arguments, la documentation de vitest-browser-svelte utilise l’API createRawSnippet de Svelte pour passer un snippet directement et vérifier ce qu’il a reçu :

<!-- 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 vs mode navigateur : que choisir ?

Choisissez jsdom + @testing-library/svelte pour des tests rapides et sans navigateur portant sur le balisage et la logique ; choisissez vitest-browser-svelte lorsque vous avez besoin de vraies API navigateur — mise en page, focus, IntersectionObserver — sans avoir à les simuler.

jsdom + testing-libraryvitest-browser-svelte
EnvironnementDOM simulé (jsdom)Vrai navigateur via Playwright
Vitesse / configurationRapide, pas de téléchargement de navigateurPlus lourd par test ; nécessite un navigateur
API navigateurÉmulées / simuléesNatives, sans simulation
Vidage synchroneNécessite souvent flushSyncLes locators réessaient automatiquement ; rarement nécessaire
PrérequisSupport Svelte 3/4/5Vitest 4

Étant donné que les locators en mode navigateur et expect.element réessaient jusqu’à ce que l’assertion réussisse, vous avez rarement besoin de flushSync dans les tests de composants — bien que quelques cas particuliers l’exigent encore. Conservez la logique réactive pure dans des fichiers jsdom .svelte.test pour la rapidité, et réservez le mode navigateur aux comportements qui dépendent d’un vrai moteur de rendu.

Commencez par extraire la logique dans des modules .svelte.js et testez-la de manière isolée, ajoutez des tests de composants jsdom via le plugin svelteTesting, et recourez à vitest-browser-svelte uniquement lorsqu’un test nécessite réellement un vrai navigateur. Configurez votre environnement une seule fois, ancrez-vous aux API actuelles présentées ci-dessus, et votre suite de tests Svelte 5 restera à l’écart des patterns Svelte 4 supprimés qui font échouer la plupart des anciens tutoriels.

Questions fréquentes

Pourquoi mon $effect ne s'exécute-t-il pas dans un test Vitest ?

Les effets ne s'exécutent pas de manière synchrone dans les tests, donc une assertion placée juste après un changement d'état voit des valeurs obsolètes. Encapsulez le code utilisant l'effet dans $effect.root() et appelez flushSync() depuis svelte pour vider les effets en attente avant d'effectuer vos assertions. Appelez la fonction de nettoyage retournée par $effect.root() à la fin du test. Dans les tests en mode navigateur, vous avez rarement besoin de cela car les locators et expect.element réessaient automatiquement, bien que quelques cas particuliers nécessitent encore flushSync.

Ai-je encore besoin de svelte-jester pour tester les composants Svelte 5 ?

Non — svelte-jester est la solution réservée à Jest et n'est pas nécessaire avec Vitest. Pour Vitest, ajoutez le plugin svelteTesting depuis @testing-library/svelte/vite, qui définit la condition de résolution browser et nettoie automatiquement le DOM après chaque test. svelte-jester apparaît toujours dans la documentation de testing-library comme solution de repli pour Jest, mais si vous utilisez Vitest, vous pouvez l'ignorer. De nombreux anciens tutoriels copient la configuration Jest, ce qui entraîne des échecs de configuration inutiles.

Puis-je tester les runes Svelte 5 dans un fichier .test.js ordinaire ?

Non. Les runes ne s'exécutent qu'après que le compilateur Svelte a traité le fichier, et le compilateur ne traite que les fichiers dont le nom contient .svelte. Pour tester les runes directement, nommez le fichier avec .svelte, par exemple counter.svelte.test.js, afin que le compilateur transforme les runes avant que Vitest n'exécute les assertions. La même règle s'applique aux modules simples qui utilisent des runes : nommez-les avec .svelte, comme counter.svelte.js, et importez-les normalement dans vos tests.

Quelle version de Vitest vitest-browser-svelte requiert-il ?

vitest-browser-svelte nécessite Vitest 4.0.0 ou supérieur ; son installation avec Vitest 3 ou antérieur échoue. Le Browser Mode est devenu stable dans Vitest 4, qui a également déplacé les packages de providers vers des installations séparées — vous importez playwright() depuis @vitest/browser-playwright et configurez un tableau instances. L'ancienne forme provider: 'playwright', name: 'chromium' de Vitest 2 est dépréciée et n'est plus correcte. Le package est hébergé sous l'organisation vitest-community sur 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.