Setting up a Svelte test environment
Tags: svelte, testing, tdd, series
Published: 2020-01-12 14:04:17

---
title: Setting up a Svelte test environment
published: true
description: Learn how to set up a Svelte project for unit testing.
tags: svelte, testing, tdd, series
series: Writing unit tests for Svelte
---

In this part, we’ll look at all the NPM packages and config that’s need to get a basic test environment running. By the end of it you’ll know enough to run `npm test` to run all specs, and `npm test <spec file>` to run a single spec file in isolation.

Remember that you can look at all this code in the demo repo.

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

## An overview of the process

A Svelte unit testing set up makes use of the following:

 * Node, because we’ll run our tests on the command line, outside of a browser.
 * Jasmine, which executes our test scripts. (If you find this a poor choice for yourself then feel free to replace it with Mocha, Jest, or anything else.)
 * JSDOM, which provides a DOM for Svelte to operate against in the Node environment.
 * Rollup, for bundling all our test files into a single CommonJS file that Node can run. This is where Svelte testing differs from React testing, and we’ll look at this in detail later.
 * Scripty, which makes our `package.json` clearer when we come to define our test build & execute script.

It’s worth repeating how this differs from something like React:

**Calling `npm test` on the command line will cause test files to be bundled with Rollup into a single file, transpiled to CommonJS, and then that single file is passed to the test runner.**

For React unit testing, it's more normal to avoid any bundler: the test runner is passed each specific spec file. Instead, Babel transpile each  file as it is loaded by the Node module loader.

More on this later.

### Required packages

Here’s a list of required packages, together with an explanation of why that package is necessary. You can install _all_ of these as dev dependencies using the `npm install --save-dev` command, or you can copy them straight out of the [demo repo’s `package.json`](https://github.com/dirv/svelte-testing-demo/blob/master/package.json).

| Package name | Why? |
| ------------ | ---- |
| `svelte` | Because it’s the framework we’re testing. |
| `rollup` | This is the standard Svelte bundler and it’s critical to what we’re doing. Webpack is _not_ necessary! |
| `jasmine` | The test runner we’ll be using. You can safely replace this with `mocha`, `jest` 😜 |
| `scripty` | This allows us to easily specify complicated `npm script` commands in their own files rather than jammed into the `package.json` file. We’ll need this for our `npm test` command. |
| `source-map-support` | This helps our test runner convert exception stack trace locations from the build output back to their source files. |
| `jsdom` | Gives us a DOM API for use in the Node environment. We won’t use this until the next part in the series. |
| `serve` | This is a web server that serves our build output for “manual” testing. We will use in this part but not any of the subsequent parts. |

Then there are some rollup plugins. Only the first three are really necessary for following this guide, but the rest are essential for any real-world Svelte development.

| Package name | Why? |
| ------------ | ---- |
| `rollup-plugin-svelte` | Compiles Svelte components into ES6 modules. |
| `@rollup/plugin-multi-entry` | Allows Rollup to roll up multiple source files into out output file. Usually it takes one input file plus its dependencies and converts that into one output file, but for our tests we’ll feed it _all_ of our spec files at once.|
| `@rollup/plugin-node-resolve` | Resolve packages in `node_modules`. |
| `@rollup/plugin-commonjs` | Allow Rollup to pull in CommonJS files, which is almost every NPM package out there. It’s generally necessary if you use any non-Svelte package. |
| `rollup-plugin-livereload` | Improve your “manual” test experience by shortening the feedback loop between code changes and visual verification of the change. We won’t use this, but we will configure it. |
| `rollup-plugin-terser` | Minifies code. Useful when you’re creating production builds.  Again, we won’t use this, but we will configure it. |

*Note*: I’m purposefully leaving out mock setup as that’s coming in a later part of the series, but to save you the suspense, I use [babel-plugin-rewire-exports](https://github.com/asapach/babel-plugin-rewire-exports) together with my own package [svelte-component-double](https://github.com/dirv/svelte-component-double).

### Rollup configuration

Our setup uses the standard `rollup.config.js`. Nothing changes there.

But what about our tests? How do they get built? Well, for that we use a separate configuration file. It uses a different set of plugin and imports (it doesn’t need `serve` configuration for example, or a startup script, or `livereload` or `terser` support).

Here is `rollup.test.config.js`:

```javascript
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import multi from "@rollup/plugin-multi-entry";
import svelte from "rollup-plugin-svelte";

export default {
  input: "spec/**/*.spec.js",
  output: {
    sourcemap: true,
    format: "cjs",
    name: "tests",
    file: "build/bundle-tests.js"
  },
  plugins: [
    multi(),
    svelte({ css: false, dev: true }),
    resolve({
      only: [/^svelte-/]
    }),
    commonjs()
  ],
  onwarn (warning, warn) {
    if (warning.code === "UNRESOLVED_IMPORT") return;
    warn(warning);
  }
};

```

A few things to note about this:

 * The output format is `cjs`, because we’ll be running in Node, not the browser.
 * The input format is a glob (`spec/**/*.spec.js`) which will match all our test files as long as they match that glob. This isn’t standard Rollup behavior. It’s enabled with the `multi` import.
 * The output it written to `./build` rather than `./public/build`. These files will never be loaded in the browser.
 * The Svelte compiler gets passed a `dev` value of true and `css` as false (I don’t write unit tests for CSS).
 * The `onwarn` call supresses [`UNRESOLVED_IMPORT` warnings](https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency). This happens because the `resolve` call is set up to `only` import packages that start with the word `svelte`. That’s important because `svelte` packages are the ones that tend to be ES6 modules. Other NPM modules are CommonJS, so it’s fine to let Node load them itself, rather than letting Rollup bundle them.

It’s worth discussing that last point a little bit more, as I’ve struggled with it. I’m no expert on JavaScript modules, and this whole experience has left me scratching my head on my occasions. Trying to learn about modules format, like CommonsJS and ES6, is not simple. The state of play is constantly changing, particular as Node’s support for ES6 modules is _almost_ out the door. More on that later.

Here’s what I’ve learned:

 * Rollup is good at bundling ES6 modules together. That’s what it’s designed to do. It stitches together ES6 `import`s and `export`s.
 * As a niceity, Rollup also transpiles your bundle to CommonJS once it’s done, which is what Node understands by default.
 * Node by default treats all NPM packages as CommonJS, unless they have `"type": "module"` defined in their `package.json`, in which case they are [treated as ES6 modules](https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency). This is very new to Node and most packages don’t yet follow this practice, even if they do in fact contain ES6 module code.
 * Svelte NPM packages are ES6 modules, but most won’t have had a chance yet to add this new `type` field.
 * Rollup assumes that every file it is given as input is an ES6 module, so it happily bundles Svelte NPM packages.
 * The `@rollup/plugin-commonjs` plugin generally does a good job of converting CommonJS modules to ES6 for bundling (which it will later transpile convert back to CommonJS 😆) _but_ it trips up on some packages (for example, `sinon`) for reasons that are beyond my level of understanding. I believe that some of the [`plugin-commonjs` options](https://github.com/rollup/plugins/tree/master/packages/commonjs) could be used to solve this, but I chose another route...

Instead, I set my config up in the way you see above. Rollup _only_ gets to bundle packages that are prefixed with `svelte-`. Everything else gets passed to NPM, and we suppress warnings about unresolved exports.

To this point this has worked for me but I’m sure this approach won’t work forever&mdash;if you’ve any opinions please do reach out in the comments.

Let’s move on for now...

### The `test` script

The [scripty](https://github.com/testdouble/scripty) package allows us to extract non-trivial scripts out of `package.json`.

Here’s `scripts/test` (Scripty pull files from the `scripts` directory).

```bash
#!/usr/bin/env sh

if [ -z $1 ]; then
  npx rollup -c rollup.test.config.js
else
  npx rollup -c rollup.test.config.js -i $1
fi

if [ $? == 0 ]; then
  npx jasmine
fi
```

This script does two things: first, it bundles the tests using `npx rollup`, and second, it runs the test file using `npx jasmine`, assuming that the first step was successful.

You can specify an argument if you wish, in which case the `-i` option gets passed to Rollup and it uses only the file provided as input.

To make this script work, `package.json` needs to have its `test` script updated as follows:

```json
"scripts": {
  "test": "scripty"
}
```

Now your tests can be run with either of these two commands:

```javascript
npm test                           # To run all test files
npm test spec/MyComponent.spec.js  # To run just a single test file
```

### Jasmine configuration

The final part is configuring Jasmine, which happens in the file `spec/support/jasmine.json`:

```json
{
  "spec_dir": ".",
  "spec_files": [
    "build/bundle-tests.js"
  ],
  "helpers": [
    "node_modules/source-map-support/register.js"
  ],
  "random": false
}
```

There are two important things here:

 * The spec file is always given as `build/bundle-tests.js`. This never changes. It’s up to Rollup to change the contents of this file depending on whether you’re testing all files or just a single file.
 * We enabled `source-map-support` by registering it here. This ensures stack traces are converted from the bundled file to the original source files.

### Mocha setup

If you’re using Mocha, you’d put this in your `package.json`:

```json
  "mocha": {
    "spec": "build/bundle-tests.js"
  }
```

And you’d change the final line of `scripts/test` to read as follows:

```bash
npx mocha -- --require source-map-support/register
```

## Testing it out

Time to try it out. The repository has a Svelte component within the file `src/HelloComponent.svelte`:

```html
<p>Hello, world!</p>
```

Yes&mdash;Svelte components can be plain HTML.

We don’t yet have any means to mount this component. But we can at least check that it is indeed a Svelte component.

Here’s `spec/HelloComponent.spec.js` that does just that.

```javascript
import HelloComponent from "../src/HelloComponent.svelte";

describe(HelloComponent.name, () => {
  it("can be instantiated", () => {
    new HelloComponent();
  });
});
```

Try it out with a call to `npm test` (or `npm test spec/HelloComponent.spec.js`):

```
created build/bundle-tests.js in 376ms
Started
F

Failures:
1) HelloComponent can be instantiated
  Message:
    Error: 'target' is a required option
  Stack:
    Error: 'target' is a required option
        at new SvelteComponentDev (/Users/daniel/svelte-testing-demo/node_modules/svelte/internal/index.mjs:1504:19)
        at new HelloComponent (/Users/daniel/svelte-testing-demo/build/bundle-tests.js:282:5)
        at UserContext.<anonymous> (/Users/daniel/svelte-testing-demo/spec/HelloComponent.spec.js:5:5)
        at <Jasmine>
        at processImmediate (internal/timers.js:439:21)
```

This error looks about right to me. Svelte needs a `target` option when instantiating a root component. For that we’ll need a DOM component. We’ll use JSDOM to create that in the next part of this series.

### Is Rollup really necessary?

To wrap up this part, I thought I’d explain a little more about Rollup. I had never encountered this before, having led a relatively sheltered React + Webpack existence up until this point.

I’ll admit, using Rollup in front of my tests like this felt like the option of last resort. It was so different from what I’d done before, with having Babel transpile my ES6 source files individually when Node required them. Babel did that by hooking into the `require` function, and all was fine.

I tried to make this work without Rollup:

 * I tried to hook into `require` myself and call the Svelte compiler directly. That works until your Svelte components reference Svelte component in other packages. This won’t work because Svelte NPM packages are ES6 by defaultt, and Node can’t handle these. Only Rollup knows how to manage these packages.
 * I even wrote [an experimental ES6 test runner called concise-test](https://github.com/dirv/concise-test) so that I could load ES6-only files. But this doesn’t work because the Node ES6 module loader hooks API is still too immature and I couldn’t get it to compile Svelte files correctly.
 * I _thought about_ using Babel to compile Svelte, or using Webpack to compile and bundle, but both of these seemed going _extremely_ off-piste, even for me.

I finally gave in to Rollup because it was only with a combination of Rollup _and Babel_ that I was able to get mocking of components working. We'll look at how that works in the last part of the series.

### Summary

In this part we look at the necessary packages and configuration for running tests. In the next part, we'll begin to write tests by mounting Svelte components into JSDOM provided containers.