Mounting components and asserting on the DOM
Tags: svelte, testing, tdd, series
Published: 2020-01-12 14:04:31

---
title: Mounting components and asserting on the DOM
published: true
description: Learn how to test what your Svelte component renders into the DOM
tags: svelte, testing, tdd, series
series: Writing unit tests for Svelte
---

In the last part we set up our testing environment ready for tests. Now we’ll set up JSDOM, mount our component under test, and then check that it rendered the right elements into the DOM tree.

As before, all this code is in the repo.

{% github dirv/svelte-testing-demo no-readme %}

Here’s a simple component, in `src/StaticComponent.js`.

```html
<script>
  export let who = "human";
</script>

<main>
  <button>Click me, {who}!</button>
<main>
```

To test this, we need to mount the component into a DOM container and then look at the rendered query output.

Before writing the test we’ll need to build up some helper functions to help us out. But here’s a sneak-peak of the first test we’ll end up writing.

```javascript
it("renders a button", () => {
  mount(StaticComponent);
  expect(container).toMatchSelector("button");
});
```

Notice the custom matcher `toMatchSelector`. In this post I’ll write a Jasmine matcher for that, which translates fairly simply to Jest and to other matcher systems too.

### Setting up JSDOM

Before mounting a component, we’ll need to ensure that the DOM is available.

The file `spec/support/svelte.js` contains the following helper function.

```javascript
import { JSDOM } from "jsdom";

const setupGlobalJsdom = (url = "https://localhost") => {
  const dom = new JSDOM("", { url, pretendToBeVisual: true });
  global.document = dom.window.document;
  global.window = { ...global.window, ...dom.window };
  global.navigator = dom.window.navigator;
};
```

This creates a new JSDOM instance and assigns it to the `document`, `window` and `navigator` globals. There’s a few interesting points:

 * The URL can be passed in which helps if you’re testing any behavior that relies on the current page location
 * The `window` object merges in the existing `window` object if it exists, which allows you to stub out functions like `window.fetch` _before_ you set up the DOM.
 * The option `pretendToBeVisual` means that the `requestAnimationFrame` API is enabled for testing, which will be useful if your app calls that function.

More information about JSDOM can be found in [the JSDOM GitHub README](https://github.com/jsdom/jsdom). One point is that they recommend against setting the document as a global in the way we are here. But unfortunately if you don’t do that, Svelte won’t work as it expects to find a global `document` instance.

### Creating a container for each test

The `spec/support/svelte.js` file also contains this function.

```javascript
const createContainer = () => {
  global.container = document.createElement("div");
  document.body.appendChild(container);
};
```

That’s straightforward enough; and what you might expect. It’s not strictly necessary to append the container to the `document.body` node, by the way. There are probably some use cases where it is necessary, but for many tests it won’t be.

Now there’s one more thing to do: define a function to call both `setupGlobalJsdom` and `createContainer`. However, it also does one more thing. Take a look.

```javascript
let mountedComponents;

export const setDomDocument = url => {
  setupGlobalJsdom(url);
  createContainer();
  mountedComponents = [];
};
```

This function also initializes a `mountedComponents` array. This array will be used to unmount all components after a test is complete. We’ll come back to that when we define the `unmount` function later.

Notice this function is also defined as an `export` so we’re ready to use it in our tests.

So how do we use this in our tests? Like this, in the file `spec/StaticComponent.spec.js`. By the way, this isn’t the end result&mdash;we’re going to improve on this later in this post.

```
import { setDomDocument } from "./support/svelte.js";

describe(StaticComponent.name, () => {
  beforeEach(() => setDomDocument());
});
```

In case you are thinking that `beforeAll` will do, I’d recommend against that. JSDOM is quick to setup and it’s always better to start each test with a clean slate.

### Mounting components

Time to define `mount`, which exists in the same file, `spec/support/svelte.js`.

It comes in two parts: a function `setBindingCallbacks`, and the `mount` function itself.

```javascript
import { bind, binding_callbacks } from "svelte/internal";

const setBindingCallbacks = (bindings, component) =>
  Object.keys(bindings).forEach(binding => {
    binding_callbacks.push(() => {
      bind(mounted, binding, value => {
        bindings[binding] = value
      });
    });
  });

export const mount = (component, props = {}, { bindings = {} } = {}) => {
  const mounted = new component({
    target: global.container,
    props
  });
  setBindingCallbacks(bindings, mounted);
  mountedComponents = [ mounted, ...mountedComponents ];
  return mounted;
};
```

The `setBindingCallbacks` is necessary for testing Svelte component bindings. The code here is plumbing that you don’t need to worry about. 

_However_, since it relies on `svelte/internal` it is subject to change and this API could break in future. I’ll come back to this in a future part; it turns out that testing bindings (both one-way and two-way) is not straightforward.

Interestingly enough, the component is _always_ mounted at the container root. The test gets no choice about that. **Each test you write will only have the option of mounting one _component under test_ at any one time.** This is standard for unit testing.

## Unmounting

Now let’s look at how we can unmount components. We should do this after each test using an `afterEach` call.

When I was unit testing React, unmounting components wasn’t always necessary. But with Svelte, I find it is pretty essential. Tests will often break subsequent tests if this isn’t done. I don’t know enough of the Svelte internals to know why that is.

By the way, if you were writing `onDestroy` handlers you’d be using this function (which also appears in `spec/support/svelte.js`) as part of the *act phase* of your test, not the arrange phase.

```javascript
export const unmountAll = () => {
  mountedComponents.forEach(component => {
    component.$destroy()
  });
  mountedComponents = [];
};
```

## Putting it together: our first test

Here’s what a first test looks like.

```javascript
import { mount, setDomDocument, unmountAll } from "./support/svelte.js";
import StaticComponent from "../src/StaticComponent.svelte";

describe(StaticComponent.name, () => {
  beforeEach(() => setDomDocument());
  afterEach(unmountAll);

  it("renders a button", () => {
    mount(StaticComponent);
    expect(container.querySelector("button")).not.toBe(null);
  });
});
```

There are two things I want to improve on this:

 1. Creating a more descriptive matcher, `toMatchSelector`, which beats a `not.toBe(null)` any day.
 2. Pull the `beforeEach` and `afterEach` into their own function as a helper.

*Quick side note:* Writing `beforeEach(setDomDocument)` won’t work as Jasmine actually passes an argument to `beforeEach` blocks, which our helper would pick up as the `url` parameter.

### Defining a `toMatchSelector` matcher

The reason this is important is so that if it fails, your test exception tells you as much useful information as possible.

Let’s take the example above to see what I mean. Taking this expectation:

```javascript
expect(container.querySelector("button")).not.toBe(null);
```

When this expectation fails, the output is this:

```
Expected null not to be null. Tip: To check for deep equality, use .toEqual() instead of .toBe().
```

This is totally useless. `Expected null not to be null`. _Great!_

How about this instead?

```
Expected container to match CSS selector "button" but it did not.
```

Much more useful. So let’s write a custom matcher to do that.

This is a [Jasmine custom matcher](https://jasmine.github.io/tutorials/custom_matcher) but a Jest custom matcher looks very similar, except it has a slightly nicer API and it’s easy to add pretty colors to the output.

This matcher also lives in `spec/support/svelte.js`.

```javascript
const toMatchSelector = (util, customEqualityTesters) => ({
  compare: (container, selector) => {
    if (container.querySelector(selector) === null) {
      return {
        pass: false,
        message: `Expected container to match CSS selector "${selector}" but it did not.`
      }
    } else {
      return {
        pass: true,
        message: `Expected container not to match CSS selector "${selector}" but it did.`
      }
    }
  }
});
```

You’ll notice I haven’t marked this as `export`, but we need some way for our test to register this matcher with Jasmine. I’m going to skip ahead and tie this in to the second of our refactorings above, of pulling the `beforeEach` and `afterEach` into their own helper.


### Defining `asSvelteComponent`

This is a real beauty:

```javascript
export const asSvelteComponent = () => {
  beforeEach(() => setDomDocument());
  beforeAll(() => {
    jasmine.addMatchers({ toMatchSelector });
  });
  afterEach(unmountAll);
};
```

Isn’t that lovely?

Now let’s rewrite our test to use this new set up:

```javascript
import { mount, asSvelteComponent } from "./support/svelte.js";
import StaticComponent from "../src/StaticComponent.svelte";

describe(StaticComponent.name, () => {
  asSvelteComponent();

  it("renders a button", () => {
    mount(StaticComponent);
    expect(container).toMatchSelector("button");
  });
});
```

Elegant, concise, and it works. Yum.

### Testing props and adding the `element` helper

To finish off this part, here’s two more tests, together with an `element` helper function.

```javascript
const element = selector => container.querySelector(selector);

it("renders a default name of human if no 'who' prop passed", () => {
  mount(StaticComponent);
  expect(element("button").textContent).toEqual("Click me, human!");
});

it("renders the passed 'who' prop in the button caption", () => {
  mount(StaticComponent, { who: "Daniel" });
  expect(element("button").textContent).toEqual("Click me, Daniel!");
});
```

I like the `element` helper because it allows our expectations to read like “proper” English.

You can also define `elements` like this:

```javascript
const elements = selector =>
  Array.from(container.querySelectorAll(selector));
```

That’s it for this part. We’ve now built up a good selection of helpers that allows us to write clear, concise tests. In the next section we’ll look at testing `onMount` callbacks.