SvelteKit uvu Testing: Fast Component Unit Tests
Tags: testing, webdev, svelte
Published: 2022-04-11 09:41:26

---
title: "SvelteKit uvu Testing: Fast Component Unit Tests"
published: "true"
description: "SvelteKit uvu testing: how you can easily add fast component and utility function unit tests to your Svelte app using uvu with TypeScript."
tags: "testing, webdev, svelte"
canonical_url: "https://rodneylab.com/sveltekit-uvu-testing/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnkf3vg77im1j3d7u5fh.png"
---

## ☑️ What is uvu?

In this post on SvelteKit uvu testing, we focus on unit testing with uvu &mdash; a **fast and flexible** test runner by Luke Edwards.  It performs a similar function to Jest but is more **lightweight** so will typically run faster.  It is handy for running **unit tests** and works with Svelte as well as **SvelteKit**.  Unit tests look at isolating a single component and checking that it generates the expected behaviour given controlled inputs. You can run unit tests with uvu on Svelte components as well as utility functions.  We shall see both.  Our focus will be on unit tests here and we use **Testing Library** to help.  We will work in **TypeScript**, but don&rsquo;t let that put you off if you are new to TypeScript, you will need to understand very little TypeScript to follow along.

## 🔥 Is Vitest faster than uvu?

Vistest is another new test runner, which can be used in a similar way to uvu.  I have seen a <a aria-label="See tests comparing test runners" href="https://github.com/oekazuma/test-runner-compare">set of tests which suggest uvu runs faster than Vitest</a>.  Both tools are under development so if speed is critical for your project it is worth running benchmarks using latest versions on your own code base.

## 😕 How is Unit Testing different to Playwright End-to-End Testing?

Integration and end-to-end testing are other types of tests.  Integration testing is a little less granular (than unit testing), combining multiple components or functions (in a way used in production) and checking they behave as expected.  End-to-end testing focuses on a final result working as an end-user would interact with them.  SvelteKit comes with some Playwright support and that is probably a better tool for end-to-end testing.  That is because Playwright is able to simulate how your app behaves in different browsers.

## 🧱 What we&rsquo;re Building

We will add a couple of unit tests to a rather basic Svelte app. It is the same one we looked at when we explored <a aria-label="Open post on Svelte each blocks" href="https://rodneylab.com/using-svelte-each-blocks">local constants in Svelte with the @const tag</a>.  The app displays not a lot more than a basic colour palette.  In brief, it displays the colour name in dark text for lighter colours and vice verse.  The objective here is to maximise contrast between the palette, or background colour and the text label.  This is something we will test works with uvu.  It also has a few utility functions, we will test one of those.  You can follow along with that app, but can just as easily create a feature branch on one of your existing SvelteKit apps and add the config we run from there.  Of course you will want to design your own unit tests.  Either way, let&rsquo;s crack on.

![SvelteKit uvu Testing: Fast Component Unit Tests: Test app screenshot shows colour palette with colour names in either black (for light palette colours) or white (for dark palette colours)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xwoskjhmaspnxv29t2h2.png)

## Config

Let&rsquo;s start by installing all the packages we will use:

```shell
pnpm add jsdom module-alias tsm uvu vite-register @testing-library/dom @testing-library/svelte @testing-library/user-event
```

You might be familiar with <a aria-label="Open the Testing Library site" href="https://testing-library.com/">Kent C Dodds&rsquo; Testing Library</a>, especially if you come from a React background.  I included it here so you can see there is a Svelte version and that it is not too different to the React version.  Although we include it in the examples, it is optional so feel free to drop it if your own project does not need it.

We&rsquo;ll now run through the config files then finally set up a handful of tests. Next stop: `package.json`.

## 📦 `package.json`

You might have noticed we added the `module-alias` package.  This will be handy here so that our tests and the files we are testing can use alias references (like `$lib`).  For it to work, we need to add a touch more configuration to `package.json` (lines `19`&ndash;`22` below).  I have added `$tests` as an additional alias; remember also to add other aliases you have defined in your project:

```json
{
  "name": "svelte-each",
  "version": "0.0.1",
  "scripts": {
    "dev": "svelte-kit dev --port 3030",
    "build": "svelte-kit build",
    "package": "svelte-kit package",
    "preview": "svelte-kit preview --port 3000",
    "prepare": "svelte-kit sync",
    "test": "playwright test",
    "test:unit": "uvu tests/lib -r tsm -r module-alias/register -r vite-register -r tests/setup/register -i setup",
    "check": "svelte-check --tsconfig ./tsconfig.json",
    "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
    "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
    "lint:css": "stylelint \"src/**/*.{css,svelte}\"",
    "prettier:check": "prettier --check --plugin-search-dir=. .",
    "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
  },
  "_moduleAliases": {
    "$lib": "src/lib",
    "$tests": "tests"
  },
  "devDependencies": {
		/* ... TRUNCATED*/
	}
```

We also have a new script for running unit tests at line `11`.  Here, immediately following `uvu` we have the test folder, I have changed this from `tests` to `tests/lib` since, depending on your project setup, you might have a dummy Playwright test in the `tests` folder.  If you already have more extensive Playwright testing (or plan to add some), you might want to move the uvu unit tests to their own folder within `tests`.  If you do this, also, change this directory in the script.

We will set up the `tests/lib` folder to mirror the `src/lib` folder.  So for example, the test for `src/lib/components/Palette.svelte` will be in `tests/lib/components/Palette.ts`.

Let's move on the the uvu config.

## ⚙️ uvu config

We&rsquo;re using the Svelte example from the uvu repo as a guide here.  In addition to that, we also have some config based on the <a href="https://github.com/basf/svelte-spectre">basf/svelte-spectre project</a>.  If your project does not already have a `tests` folder create one now in the project root.  Next, create a `setup` directory with tests and add these four files:


```typescript
import { JSDOM } from 'jsdom';
import { SvelteComponent, tick } from 'svelte';

const { window } = new JSDOM('');

export function setup() {
  global.window = window;
  global.document = window.document;
  global.navigator = window.navigator;
  global.getComputedStyle = window.getComputedStyle;
  global.requestAnimationFrame = null;
}

export function reset() {
  window.document.title = '';
  window.document.head.innerHTML = '';
  window.document.body.innerHTML = '';
}

export function render(Tag, props = {}) {
  Tag = Tag.default || Tag;
  const container: HTMLElement = window.document.body;
  const component: SvelteComponent = new Tag({ props, target: container });
  return { container, component };
}

export function fire(elem: HTMLElement, event: string, details: any): Promise<void> {
  const evt = new window.Event(event, details);
  elem.dispatchEvent(evt);
  return tick();
}
```

```javascript
import { preprocess } from 'svelte/compiler';
import { pathToFileURL } from 'url';

const { source, filename, svelteConfig } = process.env;

import(pathToFileURL(svelteConfig).toString())
  .then((configImport) => {
    const config = configImport.default ? configImport.default : configImport;
    preprocess(source, config.preprocess || {}, { filename }).then((r) =>
      process.stdout.write(r.code),
    );
  })
  .catch((err) => process.stderr.write(err));
```

```typescript
import path from 'path';
import { execSync } from 'child_process';
import { compile } from 'svelte/compiler';
import { getSvelteConfig } from './svelteconfig.mjs';

// import 'dotenv/config';

const processSync =
  (options = {}) =>
  (source, filename) => {
    const { debug, preprocess, rootMode } = options;
    let processed = source;
    if (preprocess) {
      const svelteConfig = getSvelteConfig(rootMode, filename);
      const preprocessor = require.resolve('./preprocess.js');
      processed = execSync(
        // `node -r dotenv/config --unhandled-rejections=strict --abort-on-uncaught-exception "${preprocessor}"`,
        `node -r module-alias/register --unhandled-rejections=strict --abort-on-uncaught-exception "${preprocessor}"`,
        { env: { PATH: process.env.PATH, source, filename, svelteConfig } },
      ).toString();
      if (debug) console.log(processed);
      return processed;
    } else {
      return source;
    }
  };

async function transform(hook, source, filename) {
  const { name } = path.parse(filename);
  const preprocessed = processSync({ preprocess: true })(source, filename);

  const { js, warnings } = compile(preprocessed, {
    name: name[0].toUpperCase() + name.substring(1),
    format: 'cjs',
    filename,
  });

  warnings.forEach((warning) => {
    console.warn(`\nSvelte Warning in ${warning.filename}:`);
    console.warn(warning.message);
    console.warn(warning.frame);
  });

  return hook(js.code, filename);
}

async function main() {
  const loadJS = require.extensions['.js'];

  // Runtime DOM hook for require("*.svelte") files
  // Note: for SSR/Node.js hook, use `svelte/register`
  require.extensions['.svelte'] = function (mod, filename) {
    const orig = mod._compile.bind(mod);
    mod._compile = async (code) => transform(orig, code, filename);
    loadJS(mod, filename);
  };
}

main();
```

```javascript
import { existsSync } from 'node:fs';
import { dirname, resolve, join } from 'node:path';

const configFilename = 'svelte.config.js';

export function getSvelteConfig(rootMode, filename) {
  const configDir = rootMode === 'upward' ? getConfigDir(dirname(filename)) : process.cwd();

  const configFile = resolve(configDir, configFilename);

  if (!existsSync(configFile)) {
    throw Error(`Could not find ${configFilename}`);
  }

  return configFile;
}

const getConfigDir = (searchDir) => {
  if (existsSync(join(searchDir, configFilename))) {
    return searchDir;
  }

  const parentDir = resolve(searchDir, '..');

  return parentDir !== searchDir ? getConfigDir(parentDir) : searchDir; // Stop walking at filesystem root
};
```

If you need `.env` environment variable support for your project, install the `dotenv` package.  Then uncomment line `6` in `register.ts` and replace line `18` with line `17`.

## ✅ Testing, Testing, 1, 2, 3&nbsp;&hellip;

That&rsquo;s all the config we need. Let&rsquo;s add a first test.  This will test a utility function.  The idea of the function is to help choose a text colour (either white or black) which has most contrast to the input background colour.

```typescript
import type { RGBColour } from '$lib/types/colour';
import { textColourClass } from '$lib/utilities/colour';
import { reset, setup } from '$tests/setup/env';
import { test } from 'uvu';
import assert from 'uvu/assert';

test.before(setup);
test.before.each(reset);

test('it returns expected colour class', () => {
  const blackBackground: RGBColour = { red: 0, green: 0, blue: 0 };
  assert.equal(textColourClass(blackBackground), 'text-light');

  const whiteBackground: RGBColour = { red: 255, green: 255, blue: 255 };
  assert.equal(textColourClass(whiteBackground), 'text-dark');
});

test.run();
```

Most important here is not to forget to include `test.run()` at the end&hellip; I&rsquo;ve done that a few times 😅.  Notice how we are able to use aliases in lines `1`&ndash;`3`.  You can see the <a aria-label="Open uvu docs" href="https://github.com/lukeed/uvu/blob/master/docs/api.assert.md" rel="follow noopener noreferrer">full range of assert methods available in the uvu docs</a>.

## 💯 Svelte Component Test

Let&rsquo;s do a Svelte component test, making use of snapshots and Testing Library.  Luke Edwards, who created uvu <a href="https://github.com/lukeed/uvu/issues/65">reflects his philosophy on snapshots in the project</a>.  This explains why snapshots in uvu work a little differently to what you might be familiar with in Jest.

```typescript
import Palette from '$lib/components/Palette.svelte';
import { render, reset, setup } from '$tests/setup/env';
import { render as customRender } from '@testing-library/svelte';
import { test } from 'uvu';
import assert from 'uvu/assert';

const colours = [
  { red: 0, green: 5, blue: 1 },
  { red: 247, green: 244, blue: 243 },
  { red: 255, green: 159, blue: 28 },
  { red: 48, green: 131, blue: 220 },
  { red: 186, green: 27, blue: 29 },
];

const colourSystem = 'hex';
const names = ['Deep Fir', 'Hint of Red', 'Tree Poppy', 'Curious Blue', 'Thunderbird'];

test.before(setup);
test.before.each(reset);

test('it renders', () => {
  const { container } = render(Palette, { colours, colourSystem, names });

  assert.snapshot(
    container.innerHTML,
    '<section class="colours svelte-45k0bw"><article aria-posinset="1" aria-setsize="5" class="colour text-light svelte-45k0bw" style="background-color: rgb(0, 5, 1);">Deep Fir <span class="colour-code svelte-45k0bw">#000501</span> </article><article aria-posinset="2" aria-setsize="5" class="colour text-dark svelte-45k0bw" style="background-color: rgb(247, 244, 243);">Hint of Red <span class="colour-code svelte-45k0bw">#f7f4f3</span> </article><article aria-posinset="3" aria-setsize="5" class="colour text-dark svelte-45k0bw" style="background-color: rgb(255, 159, 28);">Tree Poppy <span class="colour-code svelte-45k0bw">#ff9f1c</span> </article><article aria-posinset="4" aria-setsize="5" class="colour text-dark svelte-45k0bw" style="background-color: rgb(48, 131, 220);">Curious Blue <span class="colour-code svelte-45k0bw">#3083dc</span> </article><article aria-posinset="5" aria-setsize="5" class="colour text-light svelte-45k0bw" style="background-color: rgb(186, 27, 29);">Thunderbird <span class="colour-code svelte-45k0bw">#ba1b1d</span> </article></section>',
  );
});

test('text colour is altered to maximise contrast', () => {
  const { getByText } = customRender(Palette, { colours, colourSystem, names });
  const $lightText = getByText('Deep Fir');
  assert.is($lightText.className.includes('text-light'), true);

  const $darkText = getByText('Hint of Red');
  assert.is($darkText.className.includes('text-dark'), true);
});

test.run();
```

Note in lines `22` &amp; `31` how we import the component and its props. In lines `24`&ndash;`27` we see how you can create a snapshot.  Meanwhile in lines `3` and `31`&ndash;`33` we see how to use Testing Library with uvu.

To check the tests out, run:

```shell
pnpm run test:unit
```

from the Terminal.

![SvelteKit uvu Testing: Fast Component Unit Tests: test run screen shot of terminal: sveltekit-uvu-testing % pnpm run test:unit > svelte-each@0.0.1 test:unit sveltekit-uvu-testing > uvu tests/lib -r tsm -r module-alias/register -r vite-register -r tests/setup/register -i setup components/Palette.ts • •   (2 / 2) utilities/colour.ts •   (1 / 1) Total:     3 Passed:    3 Skipped:   0 Duration:  35.40ms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndqhx6rd4k1kd4ciqppg.png)

## 🙌🏽 SvelteKit uvu Testing: Wrapup

**In this post we looked at**:

- what uvu is and how to configure it to work with SvelteKit for testing components as well as utility functions,
- how to use Testing Library with uvu and Svelte,
- how snapshots work in uvu.

I do hope there is at least one thing in this article which you can use in your work or a side project. Also let me know if you feel more explanation of the config is needed.

You can see an <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/sveltekit-uvu-testing" rel="follow noopener noreferrer">example project with all of this setup and config on the Rodney Lab Git Hub repo</a>.  You can drop a comment below or reach for a chat on <a aria-label="Jump into the Element Matrix chat room" href="https://matrix.to/#/%23rodney:matrix.org">Element</a> as well as <a aria-label="Mention on Twitter" href="https://twitter.com/intent/user?screen_name=@askRodney">Twitter @mention</a> if you have suggestions for improvements or questions.

## 🙏🏽 Feedback

If you have found this video useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on Twitter, giving me a mention so I can see what you did. Finally be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as <a href="https://rodneylab.com/tags/seo" aria-label="See posts on search engine optimisation">Search Engine Optimisation</a> among other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.