メインコンテンツへスキップ

Svelteコンポーネントやその他のSvelte機能のテストに関する追加のリソース、パターン、およびベストプラクティスについては、Svelte Societyのテストレシピをご覧ください。

基本

この基本例では、次の方法を示します。

  • renderを使用してSvelteコンポーネントにpropsを渡す
  • screenを使用してコンポーネントのDOM要素の構造をクエリする
  • @testing-library/user-eventを使用してコンポーネントを操作する
  • @testing-library/jest-domのマッチャーを使用して、expectでアサーションを行う
greeter.svelte
<script>
export let name

let showGreeting = false

const handleClick = () => (showGreeting = true)
</script>

<button on:click="{handleClick}">Greet</button>

{#if showGreeting}
<p>Hello {name}</p>
{/if}
greeter.test.js
import {render, screen} from '@testing-library/svelte'
import userEvent from '@testing-library/user-event'
import {expect, test} from 'vitest'

import Greeter from './greeter.svelte'

test('no initial greeting', () => {
render(Greeter, {name: 'World'})

const button = screen.getByRole('button', {name: 'Greet'})
const greeting = screen.queryByText(/hello/iu)

expect(button).toBeInTheDocument()
expect(greeting).not.toBeInTheDocument()
})

test('greeting appears on click', async () => {
const user = userEvent.setup()
render(Greeter, {name: 'World'})

const button = screen.getByRole('button')
await user.click(button)
const greeting = screen.getByText(/hello world/iu)

expect(greeting).toBeInTheDocument()
})

イベント

イベントはスパイ関数を使用してテストできます。Vitestを使用している場合は、vi.fn()を使用してスパイを作成できます。

情報

イベントのテストを簡単にするために、関数propsの使用を検討してください。

button-with-event.svelte
<button on:click>click me</button>
button-with-prop.svelte
<script>
export let onClick
</script>

<button on:click="{onClick}">click me</button>
button.test.ts
import {render, screen} from '@testing-library/svelte'
import userEvent from '@testing-library/user-event'
import {expect, test, vi} from 'vitest'

import ButtonWithEvent from './button-with-event.svelte'
import ButtonWithProp from './button-with-prop.svelte'

test('button with event', async () => {
const user = userEvent.setup()
const onClick = vi.fn()

const {component} = render(ButtonWithEvent)
component.$on('click', onClick)

const button = screen.getByRole('button')
await user.click(button)

expect(onClick).toHaveBeenCalledOnce()
})

test('button with function prop', async () => {
const user = userEvent.setup()
const onClick = vi.fn()

render(ButtonWithProp, {onClick})

const button = screen.getByRole('button')
await user.click(button)

expect(onClick).toHaveBeenCalledOnce()
})

スロット

スロットは直接テストできません。通常、ユーザーに表示される結果をテストできるようにコードを構造化する方が簡単で、スロットは実装の詳細として残します。

ただし、スロットがコンポーネントの重要な開発者向けAPIである場合は、ラッパーコンポーネントと「ダミー」の子を使用してテストできます。テストIDは、この方法でスロットをテストする際に役立ちます。

heading.svelte
<h1>
<slot />
</h1>
heading.test.svelte
<script>
import Heading from './heading.svelte'
</script>

<Heading>
<span data-testid="child" />
</Heading>
heading.test.js
import {render, screen, within} from '@testing-library/svelte'
import {expect, test} from 'vitest'

import HeadingTest from './heading.test.svelte'

test('heading with slot', () => {
render(HeadingTest)

const heading = screen.getByRole('heading')
const child = within(heading).getByTestId('child')

expect(child).toBeInTheDocument()
})

双方向データバインディング

双方向データバインディングは直接テストできません。通常、ユーザーに表示される結果をテストできるようにコードを構造化する方が簡単で、バインディングは実装の詳細として残します。

ただし、双方向バインディングがコンポーネントの重要な開発者向けAPIである場合は、ラッパーコンポーネントと書き込み可能なストアを使用してバインディング自体をテストできます。

text-input.svelte
<script>
export let value = ''
</script>

<input type="text" bind:value="{value}" />
text-input.test.svelte
<script>
import TextInput from './text-input.svelte'

export let valueStore
</script>

<TextInput bind:value="{$valueStore}" />
text-input.test.js
import {render, screen} from '@testing-library/svelte'
import userEvent from '@testing-library/user-event'
import {get, writable} from 'svelte/store'
import {expect, test} from 'vitest'

import TextInputTest from './text-input.test.svelte'

test('text input with value binding', async () => {
const user = userEvent.setup()
const valueStore = writable('')

render(TextInputTest, {valueStore})

const input = screen.getByRole('textbox')
await user.type(input, 'hello world')

expect(get(valueStore)).toBe('hello world')
})

コンテキスト

コンポーネントがコンテキストへのアクセスを必要とする場合は、コンポーネントをrenderするときに、これらのコンテキストを渡すことができます。contextのようなオプションを使用する場合は、propsをpropsキーの下に配置してください。

notifications-provider.svelte
<script>
import {setContext} from 'svelte'
import {writable} from 'svelte/stores'

setContext('messages', writable([]))
</script>
notifications.svelte
<script>
import {getContext} from 'svelte'

export let label

const messages = getContext('messages')
</script>

<div role="status" aria-label="{label}">
{#each $messages as message (message.id)}
<p>{message.text}</p>
<hr />
{/each}
</div>
notifications.test.js
import {render, screen} from '@testing-library/svelte'
import {readable} from 'svelte/store'
import {expect, test} from 'vitest'

import Notifications from './notifications.svelte'

test('notifications with messages from context', async () => {
const messages = readable([
{id: 'abc', text: 'hello'},
{id: 'def', text: 'world'},
])

render(Notifications, {
context: new Map([['messages', messages]]),
props: {label: 'Notifications'},
})

const status = screen.getByRole('status', {name: 'Notifications'})

expect(status).toHaveTextContent('hello world')
})