Component Testing in Svelte
Tags: svelte, javascript
Published: 2022-02-20 04:27:45

---
title: Component Testing in Svelte
published: true
description: Testing helps us trust our application, and it's a safety net for future changes. In this tutorial, we will set up our Svelte project to run tests for our components.
tags: svelte, javascript, 
cover_image: https://images.ctfassets.net/zojzzdop0fzx/Yo9BwzX2eI3kdLgUcF7yi/082933d1c21dfaf4cc638c67c4526511/Screenshot_2021-10-15_12.24.46_PM.png
canonical_url: https://www.thisdot.co/blog/component-testing-in-svelte
---

Testing helps us trust our application, and it's a safety net for future changes. In this tutorial, we will set up our Svelte project to run tests for our components.

## Starting a new project
Let's start by creating a new project:
```bash
pnpm dlx create-vite
// Project name: › testing-svelte
// Select a framework: › svelte
// Select a variant: › svelte-ts

cd testing-svelte
pnpm install
```

There are other ways of creating a Svelte project, but I prefer using [Vite](https://vitejs.dev/). One of the reasons that I prefer using Vite is that [SvelteKit](https://kit.svelte.dev/) will use it as well. I'm also a big fan of [pnpm](https://pnpm.io/), but you can use your preferred package manager. Make sure you follow Vite's docs on starting a new project using `npm` or `yarn`.

## Installing required dependencies
- [Jest](https://jestjs.io/): I'll be using this framework for testing. It's the one that I know best, and feel more comfortable with. Because I'm using TypeScript, I need to install its type definitions too.
- [ts-jest](https://github.com/kulshekhar/ts-jest): A transformer for handling TypeScript files.
- [svelte-jester](https://github.com/mihar-22/svelte-jester): precompiles Svelte components before tests.
- [Testing Library](https://testing-library.com/): Doesn't matter what framework I'm using, I will look for an implementation of this popular library.

```bash
pnpm install --save-dev jest @types/jest @testing-library/svelte svelte-jester ts-jest
```

## Configuring tests
Now that our dependencies are installed, we need to configure jest to prepare the tests and run them.

A few steps are required:
- Convert `*.ts`  files
- Complile `*.svelte` files
- Run the tests

Create a configuration file at the root of the project:

```js
// jest.config.js
export default {
  transform: {
    '^.+\\.ts$': 'ts-jest',
    '^.+\\.svelte$': [
      'svelte-jester',
      {
        preprocess: true,
      },
    ],
  },
  moduleFileExtensions: ['js', 'ts', 'svelte'],
};

```

Jest will now use `ts-jest` for compiling `*.ts` files, and `svelte-jester` for `*.svelte` files.

## Creating a new test
Let's test the Counter component created when we started the project, but first, I'll check what our component does.

```html
<script lang="ts">
  let count: number = 0;
  const increment = () => {
    count += 1;
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>

<style>
  button {
    font-family: inherit;
    font-size: inherit;
    padding: 1em 2em;
    color: #ff3e00;
    background-color: rgba(255, 62, 0, 0.1);
    border-radius: 2em;
    border: 2px solid rgba(255, 62, 0, 0);
    outline: none;
    width: 200px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
  }

  button:focus {
    border: 2px solid #ff3e00;
  }

  button:active {
    background-color: rgba(255, 62, 0, 0.2);
  }
</style>
```

This is a very small component where a button when clicked, updates a count, and that count is reflected in the button text.
So, that's exactly what we'll be testing.

I'll create a new file `./lib/__tests__/Counter.spec.ts`

```ts
/**
 * @jest-environment jsdom
 */

import { render, fireEvent } from '@testing-library/svelte';
import Counter from '../Counter.svelte';

describe('Counter', () => {
  it('it changes count when button is clicked', async () => {
    const { getByText } = render(Counter);
    const button = getByText(/Clicks:/);
    expect(button.innerHTML).toBe('Clicks: 0');
    await fireEvent.click(button);
    expect(button.innerHTML).toBe('Clicks: 1');
  });
});
```

We are using `render` and `fireEvent` from `testing-library`. Be mindful that fireEvent returns a Promise and we need to *await* for it to be fulfilled.
I'm using the `getByText` query, to get the button being clicked.
The comment at the top, informs jest that we need to use `jsdom` as the environment. This will make things like `document` available, otherwise, `render` will not be able to mount the component. This can be set up globally in the configuration file.

What if we wanted, to test the `increment` method in our component?
If it's not an exported function, I'd suggest testing it through the rendered component itself. Otherwise, the best option is to extract that function to another file, and import it into the component.

Let's see how that works.

```ts
// lib/increment.ts
export function increment (val: number) {
    val += 1;
    return val
  };
```

```html
<!-- lib/Counter.svelte -->
<script lang="ts">
  import { increment } from './increment';
  let count: number = 0;
</script>

<button on:click={() => (count = increment(count))}>
  Clicks: {count}
</button>
<!-- ... -->
```

Our previous tests will still work, and we can add a test for our function.
```ts
// lib/__tests__/increment.spec.ts

import { increment } from '../increment';

describe('increment', () => {
  it('it returns value+1 to given value when called', async () => {
    expect(increment(0)).toBe(1);
    expect(increment(-1)).toBe(0);
    expect(increment(1.2)).toBe(2.2);
  });
});
```

In this test, there's no need to use jsdom as the test environment. We are just testing the function.

If our method was exported, we can then test it by accessing it directly.
```html
<!-- lib/Counter.svelte -->
<script lang="ts">
  let count: number = 0;
  export const increment = () => {
    count += 1;
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>
<!-- ... -->
```

```ts
// lib/__tests__/Counter.spec.ts

describe('Counter Component', () => {
 // ... other tests

  describe('increment', () => {
    it('it exports a method', async () => {
      const { component } = render(Counter);
      expect(component.increment).toBeDefined();
    });

    it('it exports a method', async () => {
      const { getByText, component } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
      await component.increment()
      expect(button.innerHTML).toBe('Clicks: 1');
    });
  });
});
```

When the method is exported, you can access it directly from the returned `component` property of the `render` function.

NOTE: I don't recommend exporting methods from the component for simplicity if they were not meant to be exported. This will make them available from the outside, and callable from other components.

## Events
If your component dispatches an event, you can test it using the `component` property returned by `render`.

To dispatch an event, we need to import and call `createEventDispatcher`, and then call the returning funtion, giving it an event name and an optional value.

```html
<!-- lib/Counter.svelte -->
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  let count: number = 0;
  export const increment = () => {
    count += 1;
    dispatch('countChanged', count);
  };
</script>

<button on:click={increment}>
  Clicks: {count}
</button>
<!-- ... -->
```

```ts
// lib/__tests__/Counter.spec.ts
// ...

  it('it emits an event', async () => {
    const { getByText, component } = render(Counter);
    const button = getByText(/Clicks:/);
    let mockEvent = jest.fn();
    component.$on('countChanged', function (event) {
      mockEvent(event.detail);
    });
    await fireEvent.click(button);

    // Some examples on what to test
    expect(mockEvent).toHaveBeenCalled(); // to check if it's been called
    expect(mockEvent).toHaveBeenCalledTimes(1); // to check how any times it's been called
    expect(mockEvent).toHaveBeenLastCalledWith(1); // to check the content of the event
    await fireEvent.click(button);
    expect(mockEvent).toHaveBeenCalledTimes(2);
    expect(mockEvent).toHaveBeenLastCalledWith(2);
  });

//...
```

For this example, I updated the component to emit an event: `countChanged`. Every time the button is clicked, the event will emit the new count.
In the test, I'm using `getByText` to select the button to click, and `component`.

Then, I'm using `component.$on(eventName)`, and mocking the callback function to test the emitted value (`event.detail`).

## Props
You can set initial props values, and modifying them using the [client-side component API](https://svelte.dev/docs#Client-side_component_API).

Let's update our component to receive the initial count value.

```html
<!-- lib/Counter.svelte -->
<script lang="ts">
// ...
  export let count: number = 0;
// ...
</script>

<!-- ... -->
```

Converting `count` to an input value requires exporting the variable declaration.

Then we can test:
- default values
- initial values
- updating values

```ts
// lib/__tests__/Counter.ts
// ...
describe('count', () => {
    it('defaults to 0', async () => {
      const { getByText } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
    });

    it('can have an initial value', async () => {
      const { getByText } = render(Counter, {props: {count: 33}});
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 33');
    });

    it('can be updated', async () => {
      const { getByText, component } = render(Counter);
      const button = getByText(/Clicks:/);
      expect(button.innerHTML).toBe('Clicks: 0');
      await component.$set({count: 41})
      expect(button.innerHTML).toBe('Clicks: 41');
    });
});
// ...
```

We are using the second argument of the render method to pass initial values to count, and we are testing it through the rendered button

To update the value, we are calling the `$set` method on `component`, which will update the rendered value on the next tick. That's why we need to *await* it.

## Wrapping up
Testing components using Jest and Testing Library can help you avoid errors when developing, and also can make you more confident when applying changes to an existing codebase. I hope this blog post is a step forward to better testing.

You can find these examples in [this repo](https://github.com/flakolefluk/unit-testing-svelte)

---

_This Dot Labs is a development consultancy focused on providing staff augmentation, architectural guidance, and consulting to companies._

_We help implement and teach modern web best practices with technologies such as React, Angular, Vue, Web Components, GraphQL, Node, and more._