Testing Patterns
Overview
This guide provides specific, actionable testing patterns for common scenarios in Svelte 5 applications.
For comprehensive best practices and philosophy, see Best Practices. For setup and configuration, see Getting Started. For Svelte 5 runes testing
($state, $derived, untrack(), flushSync()), see Runes Testing.
Component Testing
For locator patterns, handling multiple elements, role confusion fixes, component patterns (Button, Input, Modal, Dropdown), and mocking patterns, see Component Testing.
Integration Testing Patterns
Form Submission Pattern
1describe('Contact Form Integration', () => {
2 it('should handle complete form submission flow', async () => {
3 const submit_handler = vi.fn();
4 await render(ContactForm, { onsubmit: submit_handler });
5
6 // Fill out form
7 const name_input = page.getByLabelText('Name');
8 const email_input = page.getByLabelText('Email');
9 const message_input = page.getByLabelText('Message');
10
11 await name_input.fill('John Doe');
12 await email_input.fill('[email protected]');
13 await message_input.fill('Hello world');
14
15 // Submit form
16 const submit_button = page.getByRole('button', {
17 name: 'Send Message',
18 });
19 await submit_button.click();
20
21 // Verify submission
22 expect(submit_handler).toHaveBeenCalledWith({
23 name: 'John Doe',
24 email: '[email protected]',
25 message: 'Hello world',
26 });
27
28 // Verify success message
29 await expect
30 .element(page.getByText('Message sent successfully'))
31 .toBeInTheDocument();
32 });
33
34 it('should prevent submission with invalid data', async () => {
35 const submit_handler = vi.fn();
36 await render(ContactForm, { onsubmit: submit_handler });
37
38 // Try to submit empty form
39 const submit_button = page.getByRole('button', {
40 name: 'Send Message',
41 });
42 await submit_button.click();
43
44 // Should show validation errors
45 await expect
46 .element(page.getByText('Name is required'))
47 .toBeInTheDocument();
48 await expect
49 .element(page.getByText('Email is required'))
50 .toBeInTheDocument();
51
52 // Should not call submit handler
53 expect(submit_handler).not.toHaveBeenCalled();
54 });
55});Todo List Pattern
1describe('Todo List Integration', () => {
2 it('should handle complete todo lifecycle', async () => {
3 await render(TodoManager);
4
5 // Add todo
6 const input = page.getByLabelText('New todo');
7 await input.fill('Buy groceries');
8
9 const add_button = page.getByRole('button', { name: 'Add Todo' });
10 await add_button.click();
11
12 // Verify todo appears
13 const todo_item = page.getByText('Buy groceries');
14 await expect.element(todo_item).toBeInTheDocument();
15
16 // Complete todo
17 const checkbox = page.getByRole('checkbox', {
18 name: 'Mark Buy groceries as complete',
19 });
20 await checkbox.check();
21
22 // Verify completion styling
23 await expect.element(checkbox).toBeChecked();
24 await expect.element(todo_item).toHaveClass('todo-completed');
25
26 // Delete todo
27 const delete_button = page.getByRole('button', {
28 name: 'Delete Buy groceries',
29 });
30 await delete_button.click();
31
32 // Verify removal
33 await expect.element(todo_item).not.toBeInTheDocument();
34 });
35});Navigation Pattern
1describe('Navigation Integration', () => {
2 it('should navigate between pages', async () => {
3 await render(AppLayout);
4
5 // Navigate to docs
6 const docs_link = page
7 .getByRole('link', { name: 'Documentation' })
8 .first();
9 await docs_link.click();
10
11 await expect
12 .element(page.getByText('Getting Started'))
13 .toBeInTheDocument();
14
15 // Navigate to examples
16 const examples_link = page
17 .getByRole('link', { name: 'Examples' })
18 .first();
19 await examples_link.click();
20
21 await expect
22 .element(page.getByText('Example Components'))
23 .toBeInTheDocument();
24 });
25
26 it('should highlight active navigation', async () => {
27 await render(AppLayout, { current_page: '/docs' });
28
29 const docs_link = page
30 .getByRole('link', { name: 'Documentation' })
31 .first();
32 await expect.element(docs_link).toHaveClass('nav-active');
33
34 const home_link = page
35 .getByRole('link', { name: 'Home' })
36 .first();
37 await expect.element(home_link).not.toHaveClass('nav-active');
38 });
39});SSR Testing Patterns
For comprehensive SSR testing guidance including when to add SSR tests, basic patterns, layout patterns, and head/body testing, see SSR Testing.
Server Testing Patterns
For comprehensive server testing patterns including API routes, form actions, and server hooks, see Server Testing.
Error Handling Patterns
Async Error Pattern
1describe('Async Component', () => {
2 it('should handle loading states', async () => {
3 await render(AsyncDataComponent);
4
5 // Should show loading initially
6 await expect
7 .element(page.getByText('Loading...'))
8 .toBeInTheDocument();
9
10 // Wait for data to load
11 await expect
12 .element(page.getByText('Data loaded'))
13 .toBeInTheDocument();
14 await expect
15 .element(page.getByText('Loading...'))
16 .not.toBeInTheDocument();
17 });
18
19 it('should handle error states', async () => {
20 // Mock API to throw error
21 vi.mocked(fetch_data).mockRejectedValueOnce(
22 new Error('API Error'),
23 );
24
25 await render(AsyncDataComponent);
26
27 await expect
28 .element(page.getByText('Error: API Error'))
29 .toBeInTheDocument();
30 });
31});Form Error Pattern
1describe('Form Error Handling', () => {
2 it('should display server errors', async () => {
3 const submit_handler = vi.fn().mockRejectedValueOnce({
4 message: 'Server error',
5 field_errors: { email: 'Email already exists' },
6 });
7
8 await render(RegistrationForm, { onsubmit: submit_handler });
9
10 const submit_button = page.getByRole('button', {
11 name: 'Register',
12 });
13 await submit_button.click();
14
15 await expect
16 .element(page.getByText('Server error'))
17 .toBeInTheDocument();
18 await expect
19 .element(page.getByText('Email already exists'))
20 .toBeInTheDocument();
21 });
22});Performance Testing Patterns
Large List Pattern
1describe('Large List Performance', () => {
2 it('should handle large datasets', async () => {
3 const large_dataset = Array.from({ length: 1000 }, (_, i) => ({
4 id: i,
5 name: `Item ${i}`,
6 }));
7
8 await render(VirtualizedList, { items: large_dataset });
9
10 // Should render without hanging
11 await expect
12 .element(page.getByText('Item 0'))
13 .toBeInTheDocument();
14
15 // Should support scrolling
16 const list_container = page.getByTestId('list-container');
17 await list_container.scroll({ top: 5000 });
18
19 // Should render items further down
20 await expect
21 .element(page.getByText('Item 50'))
22 .toBeInTheDocument();
23 });
24});Debounced Input Pattern
1describe('Search Input Performance', () => {
2 it('should debounce search queries', async () => {
3 const search_handler = vi.fn();
4 await render(SearchInput, { onsearch: search_handler });
5
6 const input = page.getByLabelText('Search');
7
8 // Type quickly
9 await input.fill('a');
10 await input.fill('ab');
11 await input.fill('abc');
12
13 // Should not call handler immediately
14 expect(search_handler).not.toHaveBeenCalled();
15
16 // Wait for debounce
17 await new Promise((resolve) => setTimeout(resolve, 500));
18
19 // Should call handler once with final value
20 expect(search_handler).toHaveBeenCalledOnce();
21 expect(search_handler).toHaveBeenCalledWith('abc');
22 });
23});Quick Reference
Essential Patterns Checklist
- ✅ Use
page.getBy*()locators - never containers - ✅ Always await locator assertions:
await expect.element() - ✅ Use
.first(),.nth(),.last()for multiple elements - ✅ Use
untrack()for$derived:expect(untrack(() => derived_value)) - ✅ Use
force: truefor animations:await element.click({ force: true }) - ✅ Test form validation lifecycle: initial (valid) → validate → fix
- ✅ Use snake_case for variables/functions, kebab-case for files
- ✅ Handle role confusion:
textboxnotinput, check actualroleattributes
Common Fixes
- “strict mode violation”: Use
.first(),.nth(),.last() - Role confusion: Links with
role="button"are buttons, usegetByRole('button') - Input elements: Use
getByRole('textbox'), notgetByRole('input') - Form hangs: Don’t click SvelteKit form submits - test state directly
- Animation issues: Use
force: truefor click events
Anti-Patterns to Avoid
- ❌ Never use containers:
const { container } = render() - ❌ Don’t ignore strict mode violations
- ❌ Don’t assume element roles - verify with browser dev tools
- ❌ Don’t expect forms to be invalid initially
- ❌ Don’t click SvelteKit form submits in tests