From harness-claude
Tests Svelte 5 components with Vitest and @testing-library/svelte: rendering, user events, store mocking, async tick flushing.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Test Svelte 5 components with Vitest and @testing-library/svelte — render, user events, store mocking, and async tick flushing
Tests Svelte components using Testing Library with render, fireEvent, screen queries, and Vitest. Covers rendering, user interactions, props updates, events, forms, and async states.
Provides testing patterns and examples for React components, hooks, and integrations using Vitest, React Testing Library, and Jest.
Guides React component testing with Vitest/Jest setup, React Testing Library patterns, userEvent simulation, renderHook, mocking, async ops, contexts, and jest-axe accessibility.
Share bugs, ideas, or general feedback.
Test Svelte 5 components with Vitest and @testing-library/svelte — render, user events, store mocking, and async tick flushing
tick()Setup:
npm install -D vitest @testing-library/svelte @testing-library/jest-dom jsdom
// 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',
setupFiles: ['./src/tests/setup.ts'],
globals: true,
},
});
// src/tests/setup.ts
import '@testing-library/jest-dom';
Rendering components:
render from @testing-library/svelte to mount components. Query the DOM with Testing Library queries:import { render, screen } from '@testing-library/svelte';
import MyButton from '$lib/components/MyButton.svelte';
test('renders with label', () => {
render(MyButton, { props: { label: 'Click me' } });
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});
props option:render(UserCard, {
props: {
user: { id: 1, name: 'Alice', email: 'alice@example.com' },
},
});
Firing events:
fireEvent or the userEvent library for simulating interactions:import { render, screen, fireEvent } from '@testing-library/svelte';
import Counter from '$lib/components/Counter.svelte';
test('increments on click', async () => {
render(Counter);
const button = screen.getByRole('button', { name: '+' });
await fireEvent.click(button);
await fireEvent.click(button);
expect(screen.getByText('2')).toBeInTheDocument();
});
@testing-library/user-event for more realistic interactions (typing, tabbing):import userEvent from '@testing-library/user-event';
test('types in search input', async () => {
const user = userEvent.setup();
render(SearchBar);
await user.type(screen.getByRole('searchbox'), 'svelte');
expect(screen.getByDisplayValue('svelte')).toBeInTheDocument();
});
Async updates with tick:
tick() from svelte to flush Svelte's update queue:import { tick } from 'svelte';
test('shows error after failed submit', async () => {
render(LoginForm);
await fireEvent.submit(screen.getByRole('form'));
await tick();
expect(screen.getByText('Email is required')).toBeInTheDocument();
});
Testing stores:
import { count } from '$lib/stores/counter';
import CounterDisplay from '$lib/components/CounterDisplay.svelte';
test('displays current count', () => {
count.set(42);
render(CounterDisplay);
expect(screen.getByText('42')).toBeInTheDocument();
});
Mocking modules:
vi.mock to mock module imports in tests:vi.mock('$lib/services/api', () => ({
fetchUsers: vi.fn().mockResolvedValue([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]),
}));
test('renders fetched users', async () => {
render(UserList);
await tick();
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('Bob')).toBeInTheDocument();
});
Testing with context:
context option:render(MyComponent, {
context: new Map([['theme', { primary: '#3b82f6' }]]),
});
Svelte 5 vs. Svelte 4 testing differences:
Svelte 5 with @testing-library/svelte v5+ uses the new component API internally. The test API (render, screen, fireEvent) is unchanged from the user's perspective. Ensure @testing-library/svelte v5 or later for Svelte 5 components.
The Testing Library philosophy:
Prefer queries that reflect how users perceive the UI:
getByRole — most semantic (button, heading, checkbox, etc.)getByLabelText — for form fieldsgetByPlaceholderText — fallback for inputsgetByText — visible text contentgetByTestId — last resort (add data-testid to elements)Avoid testing implementation details (component state, store values) — test observable behavior.
act in Svelte:
Unlike React Testing Library, Svelte Testing Library uses tick() for flushing updates rather than act(). Most interactions already resolve automatically with await; tick() is needed when updates are triggered by direct reactive changes rather than DOM events.
Testing SvelteKit load functions:
Load functions are plain async functions — test them directly without mounting a component:
import { load } from './+page.server';
test('returns user data', async () => {
const result = await load({
params: { id: '1' },
locals: { user: mockUser },
fetch: global.fetch,
} as any);
expect(result.user.id).toBe(1);
});
Snapshot testing:
test('matches snapshot', () => {
const { container } = render(MyComponent);
expect(container).toMatchSnapshot();
});
Use sparingly — prefer behavior assertions over structural snapshots.
https://kit.svelte.dev/docs/testing