@@ -4,53 +4,275 @@ title: Example
4
4
sidebar_label : Example
5
5
---
6
6
7
- ## Component
7
+ For additional resources, patterns, and best practices about testing Svelte
8
+ components and other Svelte features, take a look at the [ Svelte Society testing
9
+ recipes] [ testing-recipes ] .
8
10
9
- ``` html
11
+ [ testing-recipes] :
12
+ https://sveltesociety.dev/recipes/testing-and-debugging/unit-testing-svelte-component
13
+
14
+ ## Basic
15
+
16
+ This basic example demonstrates how to:
17
+
18
+ - Pass props to your Svelte component using ` render `
19
+ - Query the structure of your component's DOM elements using ` screen `
20
+ - Interact with your component using [ ` @testing-library/user-event ` ] [ user-event ]
21
+ - Make assertions using ` expect ` , using matchers from
22
+ [ ` @testing-library/jest-dom ` ] [ jest-dom ]
23
+
24
+ ``` html title="greeter.svelte"
10
25
<script >
11
26
export let name
12
27
13
- let buttonText = ' Button '
28
+ let showGreeting = false
14
29
15
- function handleClick () {
16
- buttonText = ' Button Clicked'
17
- }
30
+ const handleClick = () => (showGreeting = true )
18
31
</script >
19
32
20
- <h1 >Hello {name}!</ h1 >
33
+ <button on:click = " {handleClick} " >Greet</ button >
21
34
22
- <button on:click =" {handleClick}" >{buttonText}</button >
35
+ {#if showGreeting}
36
+ <p >Hello {name}</p >
37
+ {/if}
23
38
```
24
39
25
- ## Test
40
+ ``` js title="greeter.test.js"
41
+ import {render , screen } from ' @testing-library/svelte'
42
+ import userEvent from ' @testing-library/user-event'
43
+ import {expect , test } from ' vitest'
26
44
27
- ``` js
28
- // NOTE: jest-dom adds handy assertions to Jest (and Vitest) and it is recommended, but not required.
29
- import ' @testing-library/jest-dom'
45
+ import Greeter from ' ./greeter.svelte'
30
46
31
- import {render , fireEvent , screen } from ' @testing-library/svelte'
47
+ test (' no initial greeting' , () => {
48
+ render (Greeter, {name: ' World' })
32
49
33
- import Comp from ' ../Comp'
50
+ const button = screen .getByRole (' button' , {name: ' Greet' })
51
+ const greeting = screen .queryByText (/ hello/ iu )
34
52
35
- test (' shows proper heading when rendered' , () => {
36
- render (Comp, {name: ' World' })
37
- const heading = screen .getByText (' Hello World!' )
38
- expect (heading).toBeInTheDocument ()
53
+ expect (button).toBeInTheDocument ()
54
+ expect (greeting).not .toBeInTheDocument ()
39
55
})
40
56
41
- // Note: This is as an async test as we are using `fireEvent`
42
- test (' changes button text on click' , async () => {
43
- render (Comp, {name: ' World' })
57
+ test (' greeting appears on click' , async () => {
58
+ const user = userEvent .setup ()
59
+ render (Greeter, {name: ' World' })
60
+
44
61
const button = screen .getByRole (' button' )
62
+ await user .click (button)
63
+ const greeting = screen .getByText (/ hello world/ iu )
64
+
65
+ expect (greeting).toBeInTheDocument ()
66
+ })
67
+ ```
68
+
69
+ [ user-event ] : ../user-event/intro.mdx
70
+ [ jest-dom ] : https://github.com/testing-library/jest-dom
71
+
72
+ ## Events
73
+
74
+ Events can be tested using spy functions. If you're using Vitest you can use
75
+ [ ` vi.fn() ` ] [ vi-fn ] to create a spy.
76
+
77
+ :::info
78
+
79
+ Consider using function props to make testing events easier.
80
+
81
+ :::
82
+
83
+ ``` html title="button-with-event.svelte"
84
+ <button on:click >click me</button >
85
+ ```
86
+
87
+ ``` html title="button-with-prop.svelte"
88
+ <script >
89
+ export let onClick
90
+ </script >
91
+
92
+ <button on:click =" {onClick}" >click me</button >
93
+ ```
94
+
95
+ ``` js title="button.test.ts"
96
+ import {render , screen } from ' @testing-library/svelte'
97
+ import userEvent from ' @testing-library/user-event'
98
+ import {expect , test , vi } from ' vitest'
99
+
100
+ import ButtonWithEvent from ' ./button-with-event.svelte'
101
+ import ButtonWithProp from ' ./button-with-prop.svelte'
102
+
103
+ test (' button with event' , async () => {
104
+ const user = userEvent .setup ()
105
+ const onClick = vi .fn ()
106
+
107
+ const {component } = render (ButtonWithEvent)
108
+ component .$on (' click' , onClick)
109
+
110
+ const button = screen .getByRole (' button' )
111
+ await user .click (button)
112
+
113
+ expect (onClick).toHaveBeenCalledOnce ()
114
+ })
115
+
116
+ test (' button with function prop' , async () => {
117
+ const user = userEvent .setup ()
118
+ const onClick = vi .fn ()
119
+
120
+ render (ButtonWithProp, {onClick})
121
+
122
+ const button = screen .getByRole (' button' )
123
+ await user .click (button)
124
+
125
+ expect (onClick).toHaveBeenCalledOnce ()
126
+ })
127
+ ```
128
+
129
+ [ vi-fn ] : https://vitest.dev/api/vi.html#vi-fn
130
+
131
+ ## Slots
132
+
133
+ Slots cannot be tested directly. It's usually easier to structure your code so
134
+ that you can test the user-facing results, leaving any slots as an
135
+ implementation detail.
136
+
137
+ However, if slots are an important developer-facing API of your component, you
138
+ can use a wrapper component and "dummy" children to test them. Test IDs can be
139
+ helpful when testing slots in this manner.
140
+
141
+ ``` html title="heading.svelte"
142
+ <h1 >
143
+ <slot />
144
+ </h1 >
145
+ ```
146
+
147
+ ``` html title="heading.test.svelte"
148
+ <script >
149
+ import Heading from ' ./heading.svelte'
150
+ </script >
151
+
152
+ <Heading >
153
+ <span data-testid =" child" />
154
+ </Heading >
155
+ ```
45
156
46
- // Using await when firing events is unique to the svelte testing library because
47
- // we have to wait for the next `tick` so that Svelte flushes all pending state changes.
48
- await fireEvent . click (button)
157
+ ``` js title="heading.test.js"
158
+ import { render , screen , within } from ' @testing-library/svelte '
159
+ import { expect , test } from ' vitest '
49
160
50
- expect (button).toHaveTextContent (' Button Clicked' )
161
+ import HeadingTest from ' ./heading.test.svelte'
162
+
163
+ test (' heading with slot' , () => {
164
+ render (HeadingTest)
165
+
166
+ const heading = screen .getByRole (' heading' )
167
+ const child = within (heading).getByTestId (' child' )
168
+
169
+ expect (child).toBeInTheDocument ()
51
170
})
52
171
```
53
172
54
- For additional resources, patterns and best practices about testing svelte
55
- components and other svelte features take a look at the
56
- [ Svelte Society testing recipe] ( https://sveltesociety.dev/recipes/testing-and-debugging/unit-testing-svelte-component ) .
173
+ ## Two-way data binding
174
+
175
+ Two-way data binding cannot be tested directly. It's usually easier to structure
176
+ your code so that you can test the user-facing results, leaving the binding as
177
+ an implementation detail.
178
+
179
+ However, if two-way binding is an important developer-facing API of your
180
+ component, you can use a wrapper component and writable store to test the
181
+ binding itself.
182
+
183
+ ``` html title="text-input.svelte"
184
+ <script >
185
+ export let value = ' '
186
+ </script >
187
+
188
+ <input type =" text" bind:value =" {value}" />
189
+ ```
190
+
191
+ ``` html title="text-input.test.svelte"
192
+ <script >
193
+ import TextInput from ' ./text-input.svelte'
194
+
195
+ export let valueStore
196
+ </script >
197
+
198
+ <TextInput bind:value =" {$valueStore}" />
199
+ ```
200
+
201
+ ``` js title="text-input.test.js"
202
+ import {render , screen } from ' @testing-library/svelte'
203
+ import userEvent from ' @testing-library/user-event'
204
+ import {get , writable } from ' svelte/store'
205
+ import {expect , test } from ' vitest'
206
+
207
+ import TextInputTest from ' ./text-input.test.svelte'
208
+
209
+ test (' text input with value binding' , async () => {
210
+ const user = userEvent .setup ()
211
+ const valueStore = writable (' ' )
212
+
213
+ render (TextInputTest, {valueStore})
214
+
215
+ const input = screen .getByRole (' textbox' )
216
+ await user .type (input, ' hello world' )
217
+
218
+ expect (get (valueStore)).toBe (' hello world' )
219
+ })
220
+ ```
221
+
222
+ ## Contexts
223
+
224
+ If your component requires access to contexts, you can pass those contexts in
225
+ when you [ ` render ` ] [ component-options ] the component. When you use options like
226
+ ` context ` , be sure to place props under the ` props ` key.
227
+
228
+ [ component-options ] : ./api.mdx#component-options
229
+
230
+ ``` html title="notifications-provider.svelte"
231
+ <script >
232
+ import {setContext } from ' svelte'
233
+ import {writable } from ' svelte/stores'
234
+
235
+ setContext (' messages' , writable ([]))
236
+ </script >
237
+ ```
238
+
239
+ ``` html title="notifications.svelte"
240
+ <script >
241
+ import {getContext } from ' svelte'
242
+
243
+ export let label
244
+
245
+ const messages = getContext (' messages' )
246
+ </script >
247
+
248
+ <div role =" status" aria-label =" {label}" >
249
+ {#each $messages as message (message.id)}
250
+ <p >{message.text}</p >
251
+ <hr />
252
+ {/each}
253
+ </div >
254
+ ```
255
+
256
+ ``` js title="notifications.test.js"
257
+ import {render , screen } from ' @testing-library/svelte'
258
+ import {readable } from ' svelte/store'
259
+ import {expect , test } from ' vitest'
260
+
261
+ import Notifications from ' ./notifications.svelte'
262
+
263
+ test (' notifications with messages from context' , async () => {
264
+ const messages = readable ([
265
+ {id: ' abc' , text: ' hello' },
266
+ {id: ' def' , text: ' world' },
267
+ ])
268
+
269
+ render (Notifications, {
270
+ context: new Map ([[' messages' , messages]]),
271
+ props: {label: ' Notifications' },
272
+ })
273
+
274
+ const status = screen .getByRole (' status' , {name: ' Notifications' })
275
+
276
+ expect (status).toHaveTextContent (' hello world' )
277
+ })
278
+ ```
0 commit comments