Svelte-5-Komponenten mit Vitest testen
Teste Svelte 5 Komponenten mit Vitest per mount, testing-library oder Browser Mode, plus korrektem Setup für runes, Effekte und Snippets.
Svelte 5 hat die Art und Weise geändert, wie Komponententests aufgesetzt werden: Komponenten werden nicht mehr mit new Component({ target }) instanziiert — diese Konstruktor-API wurde entfernt. Stattdessen werden Komponenten mit mount() aus svelte, render() aus @testing-library/svelte oder render() aus vitest-browser-svelte eingebunden. Runes ($state, $derived, $effect, $props) werden erst ausgeführt, nachdem der Svelte-Compiler die Datei verarbeitet hat. Daher muss das Test-Setup Testdateien durch diesen Compiler leiten. Dieser Leitfaden behandelt die aktuelle, korrekte Vitest-Konfiguration sowie die spezifischen Muster zum Testen von Runes und Komponenten in Svelte 5.
Die wichtigsten Erkenntnisse
- In Svelte 5 wurden der
new Component({ target })-Konstruktor sowie$set,$onund$destroyentfernt; Komponenten werden mitmount()aussvelteoder einemrender()-Helper eingebunden, und Props werden über$props()ausgelesen. - Um Runes direkt zu testen, muss die Datei
.svelteim Namen enthalten (z. B.counter.svelte.test.ts), damit der Compiler die Runes vor der Ausführung der Assertions durch Vitest verarbeitet. - Effects laufen nicht synchron — Code, der
$effectverwendet, muss in$effect.root()eingebettet werden;flushSync()wird aufgerufen, um ausstehende Effects vor den Assertions zu leeren. - Für
@testing-library/svelteunter Svelte 5 muss dassvelteTesting-Plugin aus@testing-library/svelte/vitehinzugefügt werden; es setzt die Browser-Resolve-Bedingung und bereinigt das DOM nach jedem Test automatisch. vitest-browser-svelteführt Komponenten über Playwright in einem echten Browser aus und erfordert Vitest 4;render(...)wird immer mitawaitaufgerufen, Elemente werden mit Locators abgefragt, und Assertions erfolgen mitawait expect.element(...).
Was hat sich beim Testen in Svelte 5 geändert?
Die meisten Svelte-Test-Tutorials im Internet stammen noch aus der Svelte-4-Ära und verwenden APIs, die nicht mehr existieren. Wenn ein Leitfaden eine Komponente mit new instanziiert, component.$set aufruft oder $$props liest, ist er veraltet. Hier ist die Migrationsübersicht:
| Svelte 4 (entfernt) | Svelte 5 (aktuell) |
|---|---|
new Component({ target }) | mount(Component, { target }) oder render(Component) |
component.$set(props) | Props an render / rerender übergeben |
component.$on / component.$destroy | Callback-Props / unmount(component) |
$$props | $props() |
fireEvent-zuerst | userEvent oder Browser-Mode-Locators |
svelte-jester-Setup | svelteTesting-Plugin (Vitest) |
Zwei Setups sind aktuell und beide valide. Das erste ist @testing-library/svelte mit jsdom — übergeordnet, vertraut und kompatibel mit Svelte 3, 4 und 5. Das zweite ist vitest-browser-svelte, das Komponenten über Playwright in einem echten Browser rendert und dabei Vitests stabilen Browser Mode nutzt. Der Browser Mode hat seinen experimentellen Status in Vitest 4 abgelegt — Tutorials, die ihn noch als experimentell bezeichnen, können ignoriert werden.
Wie konfiguriert man Vitest für Svelte-Tests?
Discover how at OpenReplay.com.
Jedes Svelte-5-Test-Setup hat eine gemeinsame Anforderung: Vitest muss die browser-Einstiegspunkte der Pakete auflösen können, obwohl es in Node ausgeführt wird. Die Svelte-Dokumentation löst dies mit resolve.conditions. Es empfiehlt sich, mit einer Basiskonfiguration zu beginnen und diese schrittweise zu erweitern.
Für Komponententests mit jsdom müssen jsdom installiert sowie die Umgebung und das svelteTesting-Plugin hinzugefügt werden:
// 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'
}
});
Das svelteTesting-Plugin setzt die Browser-Resolve-Bedingung automatisch und richtet in Vitest vor und nach jedem Test das DOM ein bzw. bereinigt es — ein manuelles afterEach(cleanup) ist daher nicht erforderlich. resolve.conditions sollte nicht zusätzlich manuell konfiguriert werden; das Plugin übernimmt dies bereits.
Für echte Browser-Tests wird Vitests Browser Mode eingesetzt. Ab Vitest 4 werden Provider-Pakete separat installiert, und die Konfiguration importiert playwright() aus @vitest/browser-playwright mit einem instances-Array — die ältere Form provider: 'playwright', name: 'chromium' ist veraltet:
// 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' }]
}
}
});
Einen Komponententest schreiben
Eine Svelte-5-Komponente liest ihre Eingaben mit $props() und hält lokalen Zustand in $state. Hier ist die Komponente, die von beiden Ansätzen getestet wird:
<!-- Counter.svelte -->
<script>
let { initial = 0 } = $props();
let count = $state(initial);
</script>
<button onclick={() => count++}>{count}</button>
Mit @testing-library/svelte wird render aufgerufen, Elemente werden nach Rolle abgefragt, Interaktionen werden mit userEvent ausgelöst, und der Klick wird mit await abgewartet:
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');
});
Sveltes eigenes mount()/unmount() ist die Low-Level-API, auf der diese Helper aufbauen. Die Dokumentation weist darauf hin, dass der direkte mount()-Ansatz „low-level und etwas fragil” ist, da er gegen exaktes innerHTML prüft — für Komponententests sollte daher ein Render-Helper bevorzugt werden.
Mit vitest-browser-svelte wird render(...) immer mit await aufgerufen, Elemente werden mit Locators abgefragt, und Assertions erfolgen mit expect.element, das automatisch wiederholt wird, bis die Assertion erfolgreich ist:
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');
});
Runes und reaktive Logik testen
Bevor eine Komponente eingebunden wird, sollte geprüft werden, ob tatsächlich ein Komponententest erforderlich ist. Die Svelte-Dokumentation empfiehlt, reaktive Logik in ein .svelte.js-Modul auszulagern und es isoliert zu testen — ohne den Overhead einer Komponente. Dieses Modul kann Runes verwenden, da sein Dateiname .svelte enthält:
// 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++; }
};
}
Das Modul kann direkt getestet werden — die Testdatei selbst muss ebenfalls .svelte im Namen enthalten, z. B. counter.svelte.test.js, damit der Compiler die Runes vor der Ausführung der Assertions durch Vitest verarbeitet:
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);
});
Effects bilden die Ausnahme: Sie laufen nicht synchron. Wenn der zu testende Code $effect verwendet, muss er in $effect.root() eingebettet werden, und flushSync() wird aufgerufen, um ausstehende Effects vor den Assertions auszuführen — genau wie in der Svelte-Testdokumentation beschrieben:
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();
});
Snippets und Props testen
Snippets sind Sveltes 5-Ersatz für Slots; sie werden mit {@render} gerendert und über $props() empfangen. Für eine Komponente, die einen children-Snippet rendert, ist der einfachste Test eine kleine Wrapper-Komponente mit einer data-testid, nach der dann abgefragt wird. Für Snippets, deren Argumente geprüft werden sollen, verwenden die vitest-browser-svelte-Docs Sveltes createRawSnippet-API, um einen Snippet direkt zu übergeben und zu prüfen, was er erhalten hat:
<!-- 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. Browser Mode: Was ist die richtige Wahl?
jsdom + @testing-library/svelte eignet sich für schnelle, browserlose Tests von Markup und Logik; vitest-browser-svelte ist die richtige Wahl, wenn echte Browser-APIs — Layout, Fokus, IntersectionObserver — ohne Mocking benötigt werden.
| jsdom + testing-library | vitest-browser-svelte | |
|---|---|---|
| Umgebung | Simuliertes DOM (jsdom) | Echter Browser via Playwright |
| Geschwindigkeit / Setup | Schnell, kein Browser-Download | Aufwendiger pro Test; Browser erforderlich |
| Browser-APIs | Shimmed / gemockt | Nativ, kein Mocking |
| Synchrones Leeren | Oft flushSync erforderlich | Locators wiederholen automatisch; selten nötig |
| Voraussetzung | Svelte 3/4/5-Unterstützung | Vitest 4 |
Da Browser-Mode-Locators und expect.element so lange wiederholen, bis die Assertion erfolgreich ist, wird flushSync in dortigen Komponententests selten benötigt — auch wenn einige Randfälle es noch erfordern. Rein reaktive Logik sollte aus Geschwindigkeitsgründen in jsdom-.svelte.test-Dateien getestet werden; der Browser Mode bleibt Verhalten vorbehalten, das eine echte Rendering-Engine erfordert.
Der empfohlene Einstieg: Logik in .svelte.js-Module auslagern und isoliert testen, jsdom-Komponententests über das svelteTesting-Plugin hinzufügen und vitest-browser-svelte einsetzen, wenn ein Test tatsächlich einen echten Browser benötigt. Die Konfiguration wird einmalig eingerichtet, die aktuellen APIs werden wie oben beschrieben verwendet, und die Svelte-5-Testsuite bleibt frei von den entfernten Svelte-4-Mustern, die die meisten älteren Tutorials zum Scheitern bringen.
Häufig gestellte Fragen
Warum wird mein $effect in einem Vitest-Test nicht ausgeführt?
Effects laufen in Tests nicht synchron, sodass eine Assertion direkt nach einer Zustandsänderung veraltete Werte sieht. Der Effect-verwendende Code muss in $effect.root() eingebettet werden, und flushSync() aus svelte wird aufgerufen, um ausstehende Effects vor den Assertions zu leeren. Die von $effect.root() zurückgegebene Cleanup-Funktion sollte am Ende des Tests aufgerufen werden. In Browser-Mode-Tests ist dies selten erforderlich, da Locators und expect.element automatisch wiederholen — einige Randfälle erfordern jedoch weiterhin flushSync.
Wird svelte-jester noch benötigt, um Svelte-5-Komponenten zu testen?
Nein — svelte-jester ist der Jest-spezifische Weg und bei Vitest nicht erforderlich. Für Vitest wird das svelteTesting-Plugin aus @testing-library/svelte/vite hinzugefügt, das die Browser-Resolve-Bedingung setzt und das DOM nach jedem Test automatisch bereinigt. svelte-jester erscheint in der testing-library-Setupdokumentation noch als Jest-Fallback, kann bei Vitest jedoch ignoriert werden. Viele ältere Tutorials übernehmen den Jest-Pfad, was zu unnötigen Setup-Fehlern führt.
Kann ich Svelte-5-Runes in einer normalen .test.js-Datei testen?
Nein. Runes werden erst ausgeführt, nachdem der Svelte-Compiler die Datei verarbeitet hat, und der Compiler verarbeitet nur Dateien, deren Name .svelte enthält. Um Runes direkt zu testen, muss die Datei .svelte im Namen tragen, z. B. counter.svelte.test.js, damit der Compiler die Runes vor der Ausführung der Assertions durch Vitest transformiert. Dieselbe Regel gilt für einfache Module, die Runes verwenden: Sie müssen mit .svelte benannt werden, z. B. counter.svelte.js, und können dann normal in Tests importiert werden.
Welche Vitest-Version erfordert vitest-browser-svelte?
vitest-browser-svelte erfordert Vitest 4.0.0 oder höher; die Installation gegen Vitest 3 oder früher schlägt fehl. Der Browser Mode wurde in Vitest 4 stabil, das auch Provider-Pakete in separate Installationen ausgelagert hat — playwright() wird aus @vitest/browser-playwright importiert und ein instances-Array konfiguriert. Die ältere Form provider: 'playwright', name: 'chromium' aus Vitest 2 ist veraltet und nicht mehr korrekt. Das Paket ist unter der vitest-community-Organisation auf GitHub zu finden.
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