Skip to content

Testing with Vitest & Testing Library

React developers typically reach for Jest with React Testing Library (RTL) for component tests. Svelte’s equivalent is Vitest + @testing-library/svelte. The Testing Library philosophy is identical — query the DOM the way a user would, fire events, assert on visible output. The APIs are almost byte-for-byte the same. The main differences are the test runner (Vitest vs Jest) and the way components are imported (.svelte files vs .tsx).

First, the components under test:

Counter.tsx
// Counter.tsx
import React from 'react';
export function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
Counter.svelte
<!-- Counter.svelte -->
<script lang="ts">
let count = $state(0);
</script>
<div>
<p>Count: {count}</p>
<button onclick={() => count++}>Increment</button>
</div>

Now the tests:

Counter.test.tsx
// Counter.test.tsx (Jest + RTL)
import { render, screen, fireEvent } from '@testing-library/react';
import { Counter } from './Counter';
test('increments count', () => {
render(<Counter />);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Increment' }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
Counter.test.ts
// Counter.test.ts (Vitest + @testing-library/svelte)
import { render, screen, fireEvent } from '@testing-library/svelte';
import Counter from './Counter.svelte';
import { describe, it, expect } from 'vitest';
describe('Counter', () => {
it('increments count', async () => {
render(Counter);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
await fireEvent.click(
screen.getByRole('button', { name: 'Increment' })
);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
});

Key differences to notice:

  • Svelte: render(Counter) — pass the component class directly (no JSX). React: render(<Counter />) — JSX element.
  • Svelte: fireEvent.click(...) must be awaited because Svelte batches DOM updates asynchronously. React RTL’s fireEvent is synchronous.
  • Everything else — screen, getByText, getByRole, toBeInTheDocument — is identical.
vitest.config.ts
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte({ hot: !process.env.VITEST })],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/setupTests.ts'],
},
});

The hot: !process.env.VITEST guard disables HMR during test runs (which would cause errors in the jsdom environment).

Terminal window
npm install --save-dev vitest @testing-library/svelte @testing-library/jest-dom jsdom

Create src/setupTests.ts:

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

Playwright works identically for both React and Svelte projects — there is nothing Svelte-specific to learn. Install and configure it the same way:

Terminal window
npm init playwright@latest
What is the Svelte equivalent of Jest for running component tests?
How do you render a Svelte component in @testing-library/svelte?
Why must fireEvent calls be awaited in @testing-library/svelte?
Which e2e testing tool works identically for both React and Svelte projects?