Unit Testing Svelte Components

Some assumptions here, thos was pulled from something i wrote and the context was different. Edits will be required.

When testing svelte components in a node environment and using them outside of a Svelte application, we will be mostly interacting programmatically with Svelte’s client-side API as well as the helpers found in @testing-library/svelte.

Creating a component

Creating a component is as simple as passing the component constructor, and any initial props, to the provided render function.

import { render } from "@testing-library/svelte";
import Button from "../src/Button.svelte";

test("should render", () => {
  const results = render(Button, { props: { label: "a button" } });

  expect(() => results.getByLabelText("a button")).not.toThrow();
});

results is an object containing a series of methods that can be used to query the rendered component in a variety of ways. You can see a list of all query methods in the testing-library documentation [link].

Changing component props

A components props can be changed or set by calling the .\$set method of the component itself. results also has a component key which contains the component instance giving us access to svelte’s full client-side API.

Svelte schedules all component updates for completion asynchronously in the next microtask. Awaiting the action that triggers that microtask will ensure that the component has been updated before proceeding through the code. This also applies to any Svelte component update, regardless of whether it is programmatically changed (via component.\$set) or via a ‘user’ action (such as clicking a button). For this to work we need to make sure that we make the test’s callback function async, so we can use await in the body.

import { render } from "@testing-library/svelte";
import Button from "../src/Button.svelte";

test("should render", async () => {
  const { getByLabelText, component } = render(Button, {
    props: { label: "a button" },
  });

  await component.$set({ label: "another button" });
  expect(() => results.getByLabelText("another button")).not.toThrow();
});

Testing component events

Component events have a different API to props and it is important we take the time to test them correctly. We will use a combination of jest mock functions and the svelte event API to make sure our component event is calling the provided handler. We can provide an event handler to a component event via a component’s .$on method. The .$on method returns and off method, if we need to remove the listener.

import { render, fireEvent } from "@testing-library/svelte";
import Button from "../src/Button.svelte";

test("events should work", () => {
  const { getByLabelText, component } = render(Button, {
    props: { label: "a button" },
  });

  const mock = Jest.fn();
  const button = getByLabelText("a button");

  component.$on("submit", mock);
  fireEvent.click(button);

  expect(mock).toHaveBeenCalled();
});

Testing slots

Slots are more difficult to test as there is no programmatic interface for working with them either inside or outside of a Svelte component. The simplest way is to create a test specific component that utilises a dynamic component to and passes in a default slot to that component which can be asserted against. In this example the component passed as a prop will be used as the containing or parent component of the slotted child content.

<script>
  export let Component;
</script>

<svelte:component this={Component}>
  <h1 data-testid="slot">Test Data</h1>
</svelte:component>

Then the component you wish to test can be passed to the constructor as a prop in order to mount the component correctly:

import { render } from "@testing-library/svelte";

import SlotTest from "./SlotTest.svelte";
import ComponentToBeTested from "./ComponentToBeTested.svelte";

test("it should render slotted content", () => {
  const { getByTestId } = render(SlotTest, {
    props: { Component: ComponentToBeTested },
  });

  expect(getByTestId("slot")).not.toThrow();
});

Testing the context API

As with slots, there is no programmatic interface for the Context API (setContext, getContext). If you are testing a component, in isolation, that would normally have a parent setting a context to be read by a child, then the simplest solution is to use a test specific parent. This is similar to the approach we used when testing slots. A test component might look something like this.

<script>
  import { setContext } from "svelte";

  export let Component;
  export let context_key;
  export let context_value;

  setContext(key, value);
</script>

<svelte:component this={Component} />

The component we wish to test looks something like this:

<script>
  import { KEY } from './Parent.svelte';

  const ctx = getContext(KEY);
</script>

<button>{ctx.title}</button>

We can test this like so:

import { render } from "@testing-library/svelte";

import ContextTest from "./ContextTest.svelte";
import ComponentToBeTested from "./ComponentToBeTested.svelte";
// Context is keyed, we need to get the actual key to ensure the correct context is selected by the child.
import { KEY } from "./OriginalParentComponent.svelte";

test("it should render slotted content", () => {
  const { getByRole } = render(ContextTest, {
    props: {
      Component: ComponentToBeTested,
      context_key: KEY,
      context_value: { title: "Hello" },
    },
  });
  const button = getByRole("button");

  expect(button.innerHTML).toBe("Hello");
});

to be written