Svelte Testing Library

repository·main·Indexed 20 days ago

https://github.com/testing-library/svelte-testing-library

A lightweight utility for testing Svelte components built on top of Svelte and @testing-library/dom. It provides functions to mount components and query rendered output, supporting features such as rendering with props, DOM querying via the screen object, simulating user interactions with @testing-library/user-event, testing two-way bindings using setters, and managing Svelte context through wrapper functions or the render context option.

Tokens
8.2K
Snippets
28
Records
39
Agent score
67%

What's inside Svelte Testing Library

  1. Use @testing-library/svelte-core to build a custom Svelte testing library

    main
    The @testing-library/svelte-core package provides a rendering core that abstracts away differences between Svelte versions. It provides a simple API to render Svelte components into the document and manage their lifecycle (mounting and cleanup). This is intended for developers who want to build their own Svelte-specific testing library rather than using the high-level @testing-library/svelte package.
  2. Prefer one-way data flow over bindable props

    main

    While bindable() props can be tested using setters, it is often better architectural practice to avoid two-way data binding. Two-way binding can make state changes difficult to reason about and test.

    Instead of using bindable(), use the following pattern:

    1. Value props: Pass data down from parent to child.
    2. Callback props: Pass changes back up from child to parent via events or functions.

    This ensures a predictable top-down data flow.

    <script>
      // Instead of bindable, use value and a callback
      let { value, onInput } = $props()
    
      const oninput = (event) => {
        onInput(event.target.value)
      }
    </script>
    
    <input type="text" {value} {oninput} />
  3. Install @testing-library/svelte

    main

    Install @testing-library/svelte as a development dependency using npm. This library supports Svelte versions 3, 4, and 5.

    For enhanced assertions, it is recommended to also install @testing-library/jest-dom to use custom jest matchers.

    npm install --save-dev @testing-library/svelte
  4. Pass context using the render option

    main

    If your component uses Svelte's standard setContext and getContext APIs with a specific key, you can inject that context directly via the context option in the render function.

    When using the context option, you must also provide any component props under the props key within the options object.

    Usage Pattern:

    • context: A Map where keys are the context identifiers and values are the context data.
    • props: An object containing the component's props.
    import { render, screen } from '@testing-library/svelte'
    import { expect, test } from 'vitest'
    import Subject from './context.svelte'
    
    test('notifications with messages from context', () => {
      const messages = {
        get current() {
          return [
            { id: 'abc', text: 'hello' },
            { id: 'def', text: 'world' },
          ]
        }
      }
    
      render(Subject, {
        context: new Map([['messages', messages]]),
        props: { label: 'Notifications' },
      })
    
      const status = screen.getByRole('status', { name: 'Notifications' })
      expect(status).toHaveTextContent('hello world')
    })
  5. Use the wrapper option with wrapperSetup()

    main

    If you use the wrapper option in setup or mount to wrap your component (for example, to provide context), you must await wrapperSetup() before rendering. Failing to do so will result in a WrapperNotSetupError. The scaffold loaded by wrapperSetup is cached and can be called multiple times safely.

    // In a beforeEach hook
    await SvelteCore.wrapperSetup()
  6. Set up Svelte Testing with Vitest

    main

    To use @testing-library/svelte with Vitest, add the svelteTesting plugin to your vite.config.js (or vitest.config.js). This plugin handles the automatic setup and cleanup of the test environment.

    // vite.config.js
    import { svelte } from '@sveltejs/vite-plugin-svelte'
    import { svelteTesting } from '@testing-library/svelte/vite'
    
    export default defineConfig({
      plugins: [
        svelte(),
        svelteTesting(),
      ]
    });
  7. Wrap a component in a parent component using `wrapper` and `wrapperProps`

    main

    If a component requires a parent component to render or operate correctly (for example, to provide Svelte context), you can use the wrapper and wrapperProps options in the render function.

    When to use:

    • When the component under test depends on a provider component (like a context provider).
    • Note: If you can provide the context directly without a provider component, use the context option instead.
    • Warning: If a component cannot be tested in isolation, consider if you are testing at the wrong level or if the component structure should be refactored for better testability.
    import { render, screen } from '@testing-library/svelte'
    import Subject from './child.svelte'
    import Wrapper from './wrapper.svelte'
    
    // Example usage:
    render(
      Subject, 
      { label: 'Notifications' }, // Component props
      { 
        wrapper: Wrapper, 
        wrapperProps: { messages: [{ id: '1', text: 'hello' }] } 
      }
    )
  8. Manage test environment cleanup

    main

    The library provides automatic cleanup in Vitest (via the svelteTesting plugin) and Jest (via global hooks).

    Manual Cleanup

    If you are using a different framework or need to manage the lifecycle manually, use the setup and cleanup functions:

    import { cleanup, render, setup } from '@testing-library/svelte'
    
    // before test
    await setup()
    
    // test
    render(/* ... */)
    
    // after test
    cleanup()

    Disabling Auto-cleanup

    • In Vitest: Set autoCleanup: false in the svelteTesting plugin configuration.
    • In Jest/Other: Set the STL_SKIP_AUTO_CLEANUP environment variable to 1.
    // Vitest manual disable
    svelteTesting({ autoCleanup: false })
  9. Understand Svelte component types and compatibility

    main

    The library provides unified types to support Svelte 3, 4, and 5.

    • Component<P, E>: Represents a compiled, imported Svelte component. It abstracts the differences between Svelte 5 (functions) and Svelte 3/4 (classes).
    • ComponentType<C>: Represents the constructor or type of an imported component. In Svelte 5, this is the component itself; in Svelte 3/4, this is the class constructor.
    • ComponentImport<C>: A convenience type for components that may be dynamically imported via import(), supporting both the component directly or an object with a default key.
    • Props<C>: Extracts the props of a component C.
  10. Basic usage of @testing-library/svelte

    main

    This example demonstrates the standard workflow for testing Svelte components using @testing-library/svelte.

    Key capabilities include:

    • Rendering components with props: Use the render() function to mount a Svelte component and pass initial props via an options object.
    • Querying the DOM: Use the screen object to find elements within the rendered component using queries like getByRole, getByText, or queryByText.
    • Simulating user interaction: Use @testing-library/user-event to simulate realistic user actions like clicks.
    • Making assertions: Use expect with matchers from @testing-library/jest-dom (e.g., toBeInTheDocument()) to verify the state of the DOM.
    import { render, screen } from '@testing-library/svelte'
    import { userEvent } from '@testing-library/user-event'
    import { expect, test } from 'vitest'
    
    import Subject from './basic.svelte'
    
    test('greeting appears on click', async () => {
      const user = userEvent.setup()
      render(Subject, { name: 'World' })
    
      const button = screen.getByRole('button')
      await user.click(button)
      const greeting = screen.getByText(/hello world/iu)
    
      expect(greeting).toBeInTheDocument()
    })
  11. Test simple Svelte snippets using a wrapper component

    main

    Since snippets are often implementation details, the easiest way to test them is to treat them as user-facing results. For simple snippets, you can use a wrapper component and "dummy" children to verify their presence. Using data-testid attributes on the dummy children helps you locate them within the rendered output using @testing-library/svelte utilities like within.

    // basic-snippet.svelte
    <script>
      let { children } = $props()
    </script>
    
    <h1>
      {@render children?.()}
    </h1>
    // basic-snippet.test.svelte
    <script>
      import Subject from './basic-snippet.svelte'
    </script>
    
    <Subject>
      <span data-testid="child"></span>
    </Subject>
    // basic-snippet.test.js
    import { render, screen, within } from '@testing-library/svelte'
    import { expect, test } from 'vitest'
    import SubjectTest from './basic-snippet.test.svelte'
    
    test('basic snippet', () => {
      render(SubjectTest)
    
      const heading = screen.getByRole('heading')
      const child = within(heading).getByTestId('child')
    
      expect(child).toBeInTheDocument()
    })
  12. Test complex snippets using `createRawSnippet`

    main

    When testing complex snippets that require checking arguments (e.g., snippets that receive data from the component), use Svelte's createRawSnippet API. This allows you to define a snippet in your test file that can intercept and validate the arguments passed to it by the component under test.

    // complex-snippet.svelte
    <script>
      let { name, message } = $props()
      const greeting = $derived(`Hello, ${name}!`)
    </script>
    
    <p>
      {@render message?.(greeting)}
    </p>
    // complex-snippet.test.js
    import { render, screen } from '@testing-library/svelte'
    import { createRawSnippet } from 'svelte'
    import { expect, test } from 'vitest'
    import Subject from './complex-snippet.svelte'
    
    test('renders greeting in message snippet', () => {
      render(Subject, {
        name: 'Alice',
        message: createRawSnippet((greeting) => ({
          render: () => `<span data-testid="message">${greeting()}</span>`,
        })),
      })
    
      const message = screen.getByTestId('message')
      expect(message).toHaveTextContent('Hello, Alice!')
    })