Testing
The SvelteKit playground uses Vitest with the Svelte Vite plugin so .svelte files are compiled at test time. Tests run in a plain Node environment — no browser required.
Install dev dependencies
pnpm add -D vitest @vitest/coverage-v8 @sveltejs/vite-plugin-svelteConfigure Vitest
Create a vitest.config.mts with the Svelte plugin, $lib alias, and $app/* mock aliases:
// vitest.config.mts
import { defineConfig } from 'vitest/config'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { resolve } from 'path'
export default defineConfig({
plugins: [svelte({ hot: false })],
resolve: {
alias: {
'$lib': resolve(__dirname, './src/lib'),
'$app/navigation': resolve(__dirname, './tests/mocks/$app/navigation.ts'),
'$app/state': resolve(__dirname, './tests/mocks/$app/state.ts'),
},
},
test: {
environment: 'node',
include: ['tests/**/*.test.{ts,tsx}'],
setupFiles: ['tests/setup.ts'],
coverage: {
reportsDirectory: 'tests/.coverage',
include: ['src/**'],
},
},
})svelte({ hot: false }) compiles .svelte files without HMR. The $lib alias maps to ./src/lib (matching SvelteKit conventions), while $app/navigation and $app/state point to mock files that replace SvelteKit runtime modules.
Mock files
$app/navigation
// tests/mocks/$app/navigation.ts
import { vi } from 'vitest'
export const goto = vi.fn()
export const invalidate = vi.fn()
export const invalidateAll = vi.fn()
export const preloadData = vi.fn()
export const preloadCode = vi.fn()
export const beforeNavigate = vi.fn()
export const afterNavigate = vi.fn()
export const onNavigate = vi.fn()$app/state
// tests/mocks/$app/state.ts
export const page = {
params: { id: 'test-id-1' },
url: new URL('http://localhost'),
route: { id: '' },
status: 200,
error: null,
data: {},
form: null,
}Setup file
The setup file mocks @ybyra/persistence/web globally since createWebDriver uses localStorage:
// tests/setup.ts
import { vi } from 'vitest'
vi.mock('@ybyra/persistence/web', () => ({
createWebDriver: vi.fn(() => ({
initialize: vi.fn(),
create: vi.fn(),
read: vi.fn(),
update: vi.fn(),
destroy: vi.fn(),
search: vi.fn(),
})),
}))Mocking lucide-svelte
The layout imports Lucide icons for action icons. Tests that import the layout (directly or transitively) need to mock lucide-svelte:
vi.mock('lucide-svelte', () => ({
Plus: 'Plus',
Eye: 'Eye',
Pencil: 'Pencil',
Save: 'Save',
X: 'X',
Trash2: 'Trash2',
}))Testing routes
Svelte 5 components are objects (not functions), so route tests verify the default export is defined:
// tests/routes/person/add.test.ts
import { describe, it, expect } from 'vitest'
describe('routes/person/add/+page', () => {
it('exports default component', async () => {
const { default: PersonAdd } = await import('../../../src/routes/person/add/+page.svelte')
expect(PersonAdd).toBeDefined()
})
})The same pattern applies to all route pages — layout, index, list, view, and edit.
Running tests
pnpm test # run all tests
pnpm test -- --coverage # run with coverage reportTest structure
tests/
mocks/
$app/
navigation.ts
state.ts
setup.ts
src/
settings/
i18n.test.ts
icons.test.ts
application/service/
personService.test.ts
domain/person/
schema.test.ts
events.test.ts
handlers.test.ts
hooks.test.ts
routes/
routes.test.ts
routes/
layout.test.ts
index.test.ts
person/
index.test.ts
add.test.ts
view.test.ts
edit.test.ts