Test Svelte Component Using Vitest & Playwright

David PengDavid Peng
Jul 28, 2022ยท
7 min read

Hi ๐Ÿ‘‹, I'm David Peng. It's been a while since my last blog post.

In the last two months, I added 100+ unit/ component/ e2e tests in my Svelte project (yeah, I didn't do TDD because I wasn't familiar with testing enough ๐Ÿ˜…). I experimented with different test runners, kept measuring DX, and tried to find my ideal toolset for testing. I'd love to share some of my learnings and thoughts here.

In this article, I'll focus on the basic setup of Vitest & Playwright to test our Svelte components. You can check the demo repo here: Svelte Component Test Demo Repo

In my next blog post, I'll also write about the advanced component test and mocking (Svelte runtime module & networking). Stay tuned!

Different types of test

Here's a brief introduction of each type (my point of view):

  • Unit Test: Test the smallest piece of code, e.g., a simple function.
  • Integration Test: Test group of functions, e.g., fetching and transforming data
  • Component Test: Test the behavior/ transition of a UI component, e.g., form interaction, open a modal
  • End-to-end (E2E) test: Complete user journey, e.g., login to a website and a series of operations

Test Runners

Vitest: A Vite-native unit test framework. (alternative: Jest, uvu)

You might hear more Svelte Developers migrating their tests from Jest to Vitest in the past few months.

Vitest is way faster than Jest because of Vite goodness, and it's a great fit with SvelteKit (use vite under the hood). Another plus is you can remove all your jest/ babel config ๐ŸŽŠ.

A great read here: Testing a Svelte app with Vitest

Playwright: A framework for Web Testing and Automation. (alternative: Cypress)

Playwright is a recommended E2E test runner in the Svelte community. You can also find Playwright as an option in create-svelte.

When to use each test runner?

Even though Vitest & Playwright are both test runners, they have a different focus and testing scenarios.

Vitest is incredibly fast because of its instant Hot Module Reload; it only reruns the test whenever the test, source code, or dependencies are changed, so it's suitable for the unit, integration tests.

While Playwright was created specifically for E2E testing, you need to build your Svelte app and start the server to test your app, which is much similar to the production.

But when it comes to the component test, we have a couple of choices:

  • Unit testing runners like Vitest, Jest, or uvu + Testing Library
  • Playwright/ Cypress component test (both powered by Vite)
  • Storybook (I haven't tried it ๐Ÿ˜)
Unit Testโœจ Component Test โœจE2E Test
Vitestvitest + @testing-library/svelte or @playwright/experimental-ct-sveltePlaywright

Let's see how to test the Svelte component using Vitest & Playwright.

Since we're going to talk about the component test, I like to share this clip from JS Party, you can also listen to the episode #233

Vitest + @testing-library/svelte

You can follow the below steps to setup Vitest or use the Svelte Adder: svelte-add-vitest

Take Svelte Demo App (TypeScript) for example

  • npm install -D vitest jsdom @testing-library/svelte
  • npm install -D @testing-library/jest-dom @types/testing-library__jest-dom (optional but recommended)
  • Configure Vitest in vite.config.js:
import { sveltekit } from '@sveltejs/kit/vite';

/** @type {import('vite').UserConfig} */
const config = {
  plugins: [sveltekit()],
  /** Add below settings */
  test: {
    // Jest like globals
    globals: true,
    environment: 'jsdom',
    include: ['src/**/*.{test,spec}.ts'],
    // Extend jest-dom matchers
    setupFiles: ['./setupTest.js']
  }
};

export default config;
  • Create setupTest.js and extend jest-dom assertions/ matchers in Vitest:
import matchers from '@testing-library/jest-dom/matchers';
import { expect, vi } from 'vitest';

expect.extend(matchers);

The reason to use jest-dom assertions can be found here: Common mistakes with React Testing Library #Using the wrong assertion

  • Add types in tsconfig.json:
{
  "compilerOptions": {
    "types": ["vitest/globals", "@testing-library/jest-dom"]
  }
}
  • Create src/lib/Counter.test.ts:
import { render, fireEvent, screen } from '@testing-library/svelte';
import Counter from './Counter.svelte';

describe('Test Counter.svelte', async () => {
  it('Initial counter should be 0', async () => {
    render(Counter);
    expect(screen.getByText('0')).toBeInTheDocument();
  });
  it('Test decrease', async () => {
    render(Counter);
    const decreaseButton = screen.getByLabelText('Decrease the counter by one');
    // Decrease by two
    await fireEvent.click(decreaseButton);
    await fireEvent.click(decreaseButton);
    // Wait for animation
    const counter = await screen.findByText('-2');
    expect(counter).toBeInTheDocument();
  });
  it('Test increase', async () => {
    render(Counter);
    const increaseButton = screen.getByLabelText('Increase the counter by one');
    // Increase by one
    await fireEvent.click(increaseButton);
    // Wait for animation
    const counter = await screen.findByText('1');
    expect(counter).toBeInTheDocument();
  });
});

The benefit of using screen is you no longer need to keep the render call destructure up-to-date as you add/remove the queries you need. You only need to type screen. And let your editor's magic autocomplete take care of the rest. (ref: Common mistakes with React Testing Library #Not using screen)

  • Add test script in package.json:
{
  "scripts": {
    "test": "vitest"
  }
}

execute vitest

Playwright Experimental Component Test

This experimental component test is powered by Vite. For more details, please read the doc and the overview video from Playwright:

Take Svelte Demo App (TypeScript) for example

  • npm create svelte my-app and choose the Demo Project
  • npm install -D @playwright/experimental-ct-svelte
  • Create playwright-ct.config.ts
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-svelte';
import { resolve } from 'node:path';

const config: PlaywrightTestConfig = {
  testDir: 'tests/component',
  use: {
    ctViteConfig: {
      resolve: {
        alias: {
          // Setup the built-in $lib alias in SvelteKit
          $lib: resolve('src/lib')
        }
      }
    }
  }
};

export default config;
  • Create several files in your Svelte project workspace:

playwright/index.html

<html lang="en">
  <body>
    <div id="root"></div>
    <script type="module" src="/playwright/index.js"></script>
  </body>
</html>

playwright/index.js

// Apply theme here, add anything your component needs at runtime here.
// Here, we import the demo project's css file
import '../src/app.css';
  • Create tests/component/Counter.test.ts

@playwright/experimental-ct-svelte wrap @playwright/test to provide an additional built-in component-testing specific fixture called mount

import { test, expect } from '@playwright/experimental-ct-svelte';
import Counter from '$lib/Counter.svelte';

test('Test Counter.svelte', async ({ mount }) => {
  const component = await mount(Counter);
  // Initial counter is "0"
  await expect(component).toContainText("0");
  // Decrease the counter
  await component.locator('[aria-label="Decrease the counter by one"]').dblclick();
  await expect(component).toContainText('-2');
  // Increase the counter
  await component.locator('[aria-label="Increase the counter by one"]').click();
  await expect(component).toContainText('-1');
});

Your VS Code may show an error like this:

ts-error-svelte

We can solve this by adding include in your tsconfig.json:

{
  "include": ["tests/**/*.ts"]
}
  • Add test:com script in package.json
{
  "scripts": {
    "test:com": "playwright test -c playwright-ct.config.ts"
  }
}

execute component test

Vitest (as test runner) + Playwright

I haven't tried this combination in my projects, but you can check the example from the Vitest GitHub repo: Use Playwright with Vitest as test runner

Wrapping Up

I've spent a considerable amount of time writing component tests using both Vitest & Playwright. Here are some of my thoughts and a reflection of DX (developer experience):

  • Vitest + Testing Library: Setting up your testing environment takes more steps and dependencies. It also took me a while to grasp the concept and best practices of Testing Library and use query/ assertions properly, but once you have more confidence, you'd have a pleasant DX.
  • Playwright Experimental Component Test: You can directly use most of Playwright's goodness like fixtures, network intercepting in your component test, and its syntax are intuitive and pretty handy. While it's still in the experimental phase, getting more stable may take a while.

In my opinion, Vitest + Testing Library would probably be a better choice at this moment. Despite that Vitest hasn't hit 1.0, my personal experience of migrating from Jest is pretty smooth. Also, Testing Library is stable and used by many companies.

Thank you for your reading. You can follow me on Twitter @davipon

Please leave your thoughts and experience below. Love to hear your feedback!

Resources

Discussion

sveltejs/kit: Vitest for unit testing #5285

Issues

Shim SvelteKit runtime import aliases / Importing $app/* fails #1485

Articles

Videos

34
Subscribe to my newsletter

Read articles from David Peng directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

David Peng
David Peng

Love Web Technologies, Product Management, and Data Visualization. Building products that enable people to be more creative & productive.