Svelte Testing Crash Course
Tags: svelte, javascript, testing, webdev
Published: 2020-09-23 17:14:38

---
title: Svelte Testing Crash Course
published: true
description: Test every aspect of your Svelte app to stay bug-free and increase the trust in your code. Forget about the whack-a-mole game with bugs.
series: Testing Crash Course
tags: svelte, javascript, testing, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/xtpxr4ypzke2exrc5wkq.png
canonical_url: https://blog.craftlab.hu/svelte-testing-crash-course-99bb4c5808a6
---

You have nearly finished your project, and only one feature is left. You implement the last one, but bugs appear in different parts of the system. You fix them, but another one pops up. You start playing a whack-a-mole game, and after multiple turns, you feel messed up. But there is a solution, a life-saver that can make the project shine again: write tests for the future and already existing features. This guarantees that working features stay bug-free.

In this tutorial, I’ll show you how to write unit, integration and end-to-end tests for Svelte applications.

For more test examples, you can take a look at [my Svelte TodoMVC implementation](https://github.com/blacksonic/todomvc-svelte).

### 1. Types

Tests have three types: unit, integration and end-to-end. These test types are often visualized as a pyramid.

![Testing Pyramid](https://dev-to-uploads.s3.amazonaws.com/i/2lvn9hknxg4s1roy6guv.png)

The pyramid indicates that tests on the lower levels are cheaper to write, faster to run and easier to maintain. Why don’t we write only unit tests then? Because tests on the upper end give us more confidence about the system and they check if the components play well together.

To summarize the difference between the types of tests: unit tests only work with a single unit (class, function) of code in isolation, integration tests check if multiple units work together as expected (component hierarchy, component + store), while end-to-end tests observe the application from the outside world (browser).

### 2. Test runner

For new projects, if you create it from [the Svelte starter repository](https://github.com/sveltejs/template) you have to manually add testing to the project. For a test-runner, I would choose [Jest](https://jestjs.io/) because Jest isn’t just a test runner, but contrary to [Mocha](https://mochajs.org/), it also includes an assertion library.

After installing the necessary packages for testing (`npm install jest babel-jest svelte-jester`) you have to configure Jest to be able to process Svelte components.

```javascript
// jest.config.js
module.exports = {
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.svelte$': 'svelte-jester'
  }
};
```

From now on unit/integration tests can be written in the src directory with `*.spec.js` or `*.test.js` suffix.

### 3. Single unit

So far, so good, but we haven’t written any tests yet. Let’s write our first unit test!

```javascript
describe('toUpperCase', () => {
  it('should convert string to upper case', () => {
    // Arrange
    const toUpperCase = info => info.toUpperCase();

    // Act
    const result = toUpperCase('Click to modify');

    // Assert
    expect(result).toEqual('CLICK TO MODIFY');
  });
});
```

The above is an example verifies if the `toUpperCase` function converts the given string to upper case.

The first task (arrange) is to get the target (here a function) into a testable state. It can mean importing the function, instantiating an object and setting its parameters. The second task is to execute that function/method (act). After the function has returned the result, we make assertions for the outcome.

Jest gives us two functions: `describe` and `it`. With the `describe` function we can organize our test cases around units: a unit can be a class, a function, component, etc. The `it` function stands for writing the actual test-case.

Jest has a built-in assertion library and with it, we can set expectations on the outcome. Jest has many different built-in assertions. These assertions, however, do not cover all use-cases. Those missing assertions can be imported with Jest’s plugin system, adding new types of assertions to the library (like [Jest Extended](https://github.com/jest-community/jest-extended) and [Jest DOM](https://github.com/testing-library/jest-dom)).

Most of the time, you will be writing unit tests for the business logic that resides outside of the component hierarchy, for example, state management or backend API handling.

### 4. Component display

The next step is to write an integration test for a component. Why is it an integration test? Because we no longer test only the Javascript code, but rather the interaction between the DOM as well as the corresponding component logic.

```html
<script>
  let info = 'Click to modify';
  const modify = () => info = 'Modified by click';
</script>

<div>
  <p class="info" data-testid="info">{info}</p>
  <button on:click={modify} data-testid="button">Modify</button>
</div>
```

The first component we test is one that displays its state and modifies the state if we click the button.

```javascript
import { render } from '@testing-library/svelte';
import Footer from './Footer.svelte';

describe('Footer', () => {
  it('should render component', () => {
    const { getByTestId } = render(Footer);

    const element = getByTestId('info');

    expect(element).toHaveTextContent('Click to modify');
    expect(element).toContainHTML('<p class="info" data-testid="info">Click to modify</p>');
    expect(element).toHaveClass('info');
    expect(element).toBeInstanceOf(HTMLParagraphElement);
  });
});
```

To render a component in a test, we can use the [Svelte Testing Library’s](https://github.com/testing-library/svelte-testing-library) `render` method. The `render` function needs a Svelte component to render. The return argument is an object containing selectors for the rendered HTML. In the example, we use the `getByTestId` method that retrieves an HTML element by its `data-testid` attribute. It has many more getter and query methods, you can find them [in the documentation](https://testing-library.com/docs/svelte-testing-library/api).

In the assertions, we can use the methods from the [Jest Dom plugin](https://github.com/testing-library/jest-dom), which extends Jests default assertion collection making HTML testing easier. The HTML assertion methods all expect an HTML node as input and access its native properties.

### 5. Component interactions

We have tested what can we see in the DOM, but we haven’t made any interactions with the component yet. We can interact with a component through the DOM and observe the changes through its content. We can trigger a click event on the button and observe the displayed text.

```javascript
import { render, fireEvent } from '@testing-library/svelte';

it('should modify the text after clicking the button', async () => {
  const { getByTestId } = render(Footer);

  const button = getByTestId('button');
  await fireEvent.click(button);
  const info = getByTestId('info');

  expect(info).toHaveTextContent('Modified by click');
});
```

We need a DOM element where the event can be triggered. The getters returned from the `render` method returns that element. The `fireEvent` object can trigger the desired events trough its methods on the element. We can check the result of the event by observing the text content as before.

### 6. Parent-child interactions

We have examined a component separately, but a real-world application consists of multiple parts. Parent components talk to their children through `props`, and children talk to their parents through events.

Let’s modify the component that it receives the display text through `props` and notifies the parent component about the modification through an event.

```html
<script>
  import { createEventDispatcher } from 'svelte';

  export let info;
  const dispatch = createEventDispatcher();
  const modify = () => dispatch('modify', 'Modified by click');
</script>

<div>
  <p class="info" data-testid="info">{info}</p>
  <button on:click={modify} data-testid="button">Modify</button>
</div>
```

In the test, we have to provide the `props` as input and check if the component emits the `modify` event.

```javascript
it('should handle interactions', async () => {
  let info = 'Click to modify';
  const { getByTestId, component } = render(Footer, { info });

  component.$on('modify', event => info = event.detail);

  const button = getByTestId('button');
  await fireEvent.click(button);

  expect(info).toEqual('Modified by click');
});
```

We pass down the `info` prop and listen to the `modify` event with the `$on` method on the component. When we trigger the click event on the button, the callback on the `$on` method is called and updates the `info` variable. The assertion at the end checks the `info` variable whether it was modified by the component's event.

### 7. Store integration

In the previous examples, the state was always inside the component. In complex applications, we need to access and mutate the same state in different locations. Svelte has a [built-in store implementation](https://svelte.dev/tutorial/writable-stores) that can help you organize state management in one place and ensure it mutates predictably.

```javascript
import { writable } from 'svelte/store';

export const createStore = () => {
  const state = writable('Click to modify');

  return {
    state,
    onModify(value) {
      state.update(() => value);
    }
  };
};
```

The store has a single state, which is the same as what we have seen on the component. We can modify the state with the `onModify` method that passes the input parameter to the states `update` method.

Let’s construct the store and write an integration test. This way, we can check if the methods play together instead of throwing errors.

```javascript
it('should modify state', () => {
  const { store, onModify } = createStore();
  let info;
  store.subscribe(value => info = value);

  onModify('Modified by click');

  expect(info).toEqual('Modified by click');
});
```

We can alter the store through the returned method or directly calling `update` on it. What we can't do is to directly access the state, instead, we have to subscribe to changes.

### 8. Routing

The simplest way of showing how to test routing inside a Svelte app is to create a component that displays content on the current route.

```html
<script>
  import { Router, Route } from 'svelte-routing';
  import Footer from './component-display.svelte';
</script>

<Router>
  <Route path="/"><Footer /></Route>
</Router>
```

We are using the [`svelte-routing`](https://www.npmjs.com/package/svelte-routing) library. The routes are defined within the component's template with the `Route` component.

```javascript
import { render } from '@testing-library/svelte';
import Routing from './routing.svelte';

describe('Routing', () => {
  it('should render routing', () => {
    const { getByTestId } = render(Routing);

    const element = getByTestId('info');

    expect(element).toHaveTextContent('Click to modify');
  });
});
```

Testing doesn't differ from testing a basic component. However, the test framework setup needs some adjustment because libraries in Svelte are often published to NPM without transpilation. It means that components are in `svelte` files and Jest doesn't transform files within `node_modules` by default.

```javascript
module.exports = {
  transform: {
    '^.+\\.js$': 'babel-jest',
    '^.+\\.svelte$': 'svelte-jester'
  },
  transformIgnorePatterns: [
    "node_modules/(?!(svelte-routing|svelte-spa-router)/)"
  ]
};
```

The `jest.config.js` file needs the `transformIgnorePatterns` property. By default, the regular expression here tells Jest to ignore everything in `node_modules` for transpilation. With the modified pattern, we can make an exception with our routing library and the tests pass green.

### 9. HTTP requests

Initial state mutation often comes after an HTTP request. While it is tempting to let that request reach its destination in a test, it would also make the test brittle and dependant on the outside world. To avoid this, we can change the request’s implementation at runtime, which is called mocking. We will use Jest’s built-in mocking capabilities for it.

```javascript
return {
  store,
  async onModify(info) {
    const response = await axios.post('https://example.com/api', { info });
    store.update(() => response.body);
  }
};
```

We have a function: the input parameter is first sent through a POST request, and then the result is passed to the `update` method. The code becomes asynchronous and gets [Axios](https://github.com/axios/axios) as an external dependency. The external dependency will be the one we have to change (mock) before running the test.

```javascript
it('should set info coming from endpoint', async () => {
  const commit = jest.fn();
  jest.spyOn(axios, 'post').mockImplementation(() => ({
    body: 'Modified by post'
  }));

  const { store, onModify } = createStore();
  let info;
  store.subscribe(value => info = value);
  await onModify('Modified by click');

  expect(info).toEqual('Modified by post');
});
```

We are creating a fake implementation and change the original implementation of `axios.post`. These fake implementations capture the arguments passed to them and can respond with whatever we tell them to return (`mockImplementation`). `axios.post` will return with a `Promise` that resolves to an object with the `body` property.

The test function becomes asynchronous by adding the `async` modifier in front of it: Jest can detect and wait for the asynchronous function to complete. Inside the function, we wait for the `onModify` method to complete with `await` and then make an assertion whether the store is updated with the parameter returned from the post call.

### 10. The browser

From a code perspective, we have touched every aspect of the application. There is a question we still can’t answer: can the application run in the browser? End-to-end tests written with [Cypress](https://www.cypress.io/) can answer this question.

The Svelte template repository doesn’t have a built-in E2E testing solution, we have to orchestrate it manually: start the application and run the Cypress tests in the browser, and then shut down the application. It means installing Cypress for running the tests and [start-server-and-test](https://github.com/bahmutov/start-server-and-test) library to start the server. If you want to run the Cypress tests in headless mode, you have to add the `--headless` flag to the command (`cypress run --headless`).

```javascript
describe('New todo', () => {
  it('it should change info', () => {
    cy.visit('/');

    cy.contains('.info', 'Click to modify');

    cy.get('button').click();

    cy.contains('.info', 'Modified by click');
  });
});
```

The organization of the tests is the same as with unit tests: `describe` stands for grouping, `it` stands for running the tests. We have a global variable, `cy`, which represents the Cypress runner. We can command the runner synchronously about what to do in the browser.

After visiting the main page (`visit`), we can access the displayed HTML through CSS selectors. We can assert the contents of an element with contains. Interactions work the same way: first, select the element (`get`) and then make the interaction (`click`). At the end of the test, we check if the content has changed or not.

### Summary

We have reached the end of testing use-cases. I hope you enjoyed the examples and they clarified many things around testing. I wanted to lower the barrier of starting to write tests for a Svelte application. We have gone from a basic unit test for a function to an end-to-end test running in a real browser.

Through our journey, we have created integration tests for the building blocks of a Svelte application (components, store) and scratched the surface of implementation mocking. With these techniques, your existing and future projects can stay bug-free.