Vitest: Blazing fast unit test framework
Tags: webdev, javascript, testing, tutorial
Published: 2023-01-31 14:37:06

If you have heard of Vite, then you have probably heard of Vitest, the fast unit test framework built from it. In this article, let’s explore what Vitest is, how to use it and why it can be the next test framework for your apps.

## What is Vitest

Before we get into Vitest, let’s note that Vite is a build tool that allows for faster server starts and updates thanks to its native ESM-based method to serve code on demand:

![Vite dev server structure: server gets HTTP request, finds a suitable route, which leads to the exact module that should be processed and served; all of that due to dynamic imports](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p0tv5czqphiq8x6sonu8.png)

_If you want to get more, feel free to read my [Introduction to Vite](https://lo-victoria.com/introduction-to-vite-the-next-generation-frontend-tooling) article._

So Vitest is the unit testing framework built on top of Vite and is an excellent unit test framework with many modern features such as:

- Component testing for Vue, React, Svelte, Lit and more.
- Out-of-the-box TypeScript and JSX support.
- ESM first, top level `await`.
- Multithreading workers.
- Filtering, timeouts, concurrent for suite and tests.
- Jest-compatible snapshot testing.
- Chai built-in for assertions + Jest `expect` compatible APIs.
- Designed with a Jest compatible API.

Let’s get started with a simple example on how to use Vitest in a Vite-powered project.

## Example usage: writing unit tests in Vitest

### Step 1. Create a Vite project

It’s simple, just type:

```shell
npm create vite .
```

_Note: Vite requires Node.js version 14.18+ or 16+._

This will start the process to create a Vite project. You can name your project and choose a template appropriately. For this example, I will be choosing the Vanilla template and TypeScript:

```plain
$ npm create vite .
✔ Package name: … vitest
✔ Select a framework: › Vanilla
✔ Select a variant: › TypeScript

Scaffolding project in /tmp/vitest...

Done. Now run:

  npm install
  npm run dev
```

As you see, after the project is created, you can install the necessary dependencies by running:

```shell
npm install
```

Now, you should have the following project structure:

![node_modules, public & src directories, plus some files: .gitignore, index.html, package-lock.json, package.json and tsconfig.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qohij1bjys3vbe5ng6u6.png)

Finally, to install Vitest, run:

```shell
npm install -D vitest
```

#### Alternative for non-Vite powered projects

It is just as easy to setup Vitest in a non-Vite powered projects.
Say we have a <abbr title="Create React App">CRA</abbr> project and want to use Vitest for testing.

All we need to do is to run:

```shell
npm install -D vitest @vitejs/plugin-react
```

And then add a _vite.config.js_ in the root folder of the project:

```ts
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    open: true,
  },
  build: {
    outDir: 'build',
    sourcemap: true,
    commonjsOptions: {
      include: [],
    },
  },
  optimizeDeps: {
    disabled: false,
  },
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: '@testing-library/jest-dom',
    mockReset: true,
  },
});
```

### Step 2. Write some simple tests

Now that our project is set up, let us write some simple tests.

Vitest has been designed based on Jest, so it shares a lot of similarities. One of them is that it can automatically detect your test files as long as you name it in any of the 3 following formats:

1. A _.js_ file in a folder named _\_\_tests\_\_._
2. A file with a name like _[name].spec.js._
3. A file with a name like _[name].test.js._

So let’s create a new folder in our example project called _tests_ and add a _basic.test.js_ file:

```text
tests/
    |- basic.test.js
```

In this file, let’s add some simple tests:

```ts
import { describe, it, assert } from 'vitest';

describe('Math.sqrt() test', () => {
  it('SQRT4', () => {
    assert.equal(Math.sqrt(4), 2);
  });

  it('SQRT144', () => {
    assert.equal(Math.sqrt(144), 12);
  });

  it('SQRT2', () => {
    assert.equal(Math.sqrt(2), Math.SQRT2);
  });
});
```

If you have worked with other testing libraries such as Jest, Mocha or Jasmine, you should be familiar with the BDD (Behavior Driven Development) pattern. It **describes** a function, explains what **it** does and uses test case(s) to **assert** that it works as intended.

### Step 3. Run tests

To run the tests, the command is:

```shell
npx vitest
```

Or you can configure your _package.json_ to run the command with `npm test`:

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

_More Vitest CLI commands can be found at the [official documentation](https://vitest.dev/guide/cli.html)._

By default, the tests run in watch mode so if you make any file changes, they will re-run immediately. If the tests are successful, you should see the terminal outputting:

```plain
$ npx vitest

 DEV  v0.27.2 /tmp/vitest

 ✓ tests/basic.test.js (1)

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Start at  15:05:07
   Duration  933ms (transform 379ms, setup 0ms, collect 13ms, tests 2ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit
```

## Vitest VS Code extension

To speed up testing and make debugging easier,
there is a [VS Code extension for Vitest](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer) available in the Marketplace.

![VS Code extension showing the list of tests and allowing to pick which one to start](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akmaf7jx99fvfbnvsaov.png)

This extension can help to:

- filter tests by status,
- debug easier,
- inspect console output,
- rerun tests faster.

## Vitest vs Other test frameworks

Vitest is often compared to Jest, another popular test framework.
It is because it is built on top of Jest, making it a more modern and improved version. Also, it offers compatibility with most of the Jest API and ecosystem libraries, making it simple to migrate by following their official guide here.

Just like other widely used test frameworks such as Mocha and Jasmine, Vitest follows a simple **describe-it-assert** or **describe-it-expect** pattern. The advantage of using Vitest over them is that it is fast to set up and does not require installing a separate assertion library.

The most convenient advantage of using Vitest is that it requires minimal configuration and can be used with any bundler. You can define the configuration for your dev, build and test environments as a single pipeline under _vite.config.js._

A simple example would be setting up a Jest and Babel environment for a React app. Often, you would need to install additional packages besides the ones that come with <abbr title="Create React App">CRA</abbr>:

- babel-jest,
- @babel/core,
- @babel/preset-env,
- @babel/preset-react,
- @babel/preset-typescript,
- @types/jest.

After that, you would need a _jest.config.js_ and a _babel.config.js_ to complete setting up the configuration.

With Vitest, you don’t have to install those extra dependencies. All you need is a _vite.config.js_ or _vitest.config.js_ file. Even for non-Vite projects, it is a single file to configure. For example:

```ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
  },
});
```

_Learn more about configuration in the [official documentation](https://vitest.dev/config/)._

Here is a summarized comparison of different popular test frameworks with Vitest:

<table>
  <thead>
  <tr>
    <td></td>
    <th scope="col">Pros</th>
    <th scope="col">Cons</th>
  </tr>
  </thead>
  <tbody>
  <tr>
    <th scope="row">Jest</th>
    <td>
      <ul>
        <li>Compatible with most frameworks like React, Vue, and Babel-based projects.</li>
        <li>Performance is good.</li>
        <li>Supports asynchronous code and other wide range of tests.</li>
        <li>Extended API, can be optionally included when needed.</li>
        <li>Auto mock any libraries imported for easy testing.</li>
        <li>Highly active community support.</li>
      </ul>
    </td>
    <td>
      <ul>
        <li>Auto mocking can slow down performance.</li>
        <li>When used with third-party libraries or tools, it may not be supported or difficult to configure.</li>
        <li>Can require a lot of dependencies to set up (i.e. Babel projects).</li>
      </ul>
    </td>
  </tr>
  <tr>
    <th scope="row">Mocha</th>
    <td>
      <ul>
        <li>Simple, lightweight and easy to get into.</li>
        <li>Flexible to configure and include different libraries with.</li>
        <li>Active community support.</li>
      </ul>
    </td>
    <td>
      <ul>
        <li>Does not come with a lot of features and need to install other libraries separately.</li>
        <li>Can be hard to configure as different dependencies has different requirements.</li>
        <li>Can be complex to include auto mocking or snapshot testing.</li>
      </ul>
    </td>
  </tr>
  <tr>
    <th scope="row">Jasmine</th>
    <td>
      <ul>
        <li>Very flexible and is compatible with most frameworks and libraries.</li>
        <li>Built-in assertion library, so it doesn’t require additional libraries.</li>
        <li>Easy to read and clean syntax.</li>
      </ul>
    </td>
    <td>
      <ul>
        <li>Difficult to test asynchronous code.</li>
        <li>Not as widely used as newer test frameworks.</li>
      </ul>
    </td>
  </tr>
  <tr>
    <th scope="row">Vitest</th>
    <td>
      <ul>
        <li>Native ESM and out-of-box TypeScript support.</li>
        <li>Multithreaded and lightweight.</li>
        <li>Minimal configuration and setup needed.</li>
        <li>Build upon Jest API, includes all Jest testing features.</li>
        <li>Performance is fast, live reloads are faster than Jest.</li>
        <li>Highly compatible with Vite projects.</li>
      </ul>
    </td>
    <td>
      <ul>
        <li>Still in early phase, may not have active community support.</li>
        <li>Does not use a global module like Jest and isolates each test to run, may affect performance when scaling.</li>
      </ul>
    </td>
  </tr>
  </tbody>
</table>

## More features. Test coverage report

Let’s see a simple example how we can output a test coverage report. First, let’s create a simple counter function in _counter.js:_

```ts
export const setCounter = (count) => {
  return count + 1;
};
```

Then, let’s create a test for this function in the _counter.test.js:_

```ts
import { describe, it, expect } from 'vitest';
import { setCounter } from '../src/counter';

describe('setCounter', () => {
  it('returns 1', () => {
    expect(setCounter(0)).toBe(1);
  });
});
```

Now if we run `vitest run --coverage`, we can see how much of our code has been tested:

```plain
$ vitest run --coverage

 RUN  v0.27.2 /tmp/vitest
      Coverage enabled with c8

 ✓ tests/counter.test.js (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  15:19:08
   Duration  2.12s (transform 390ms, setup 0ms, collect 40ms, tests 2ms)

 % Coverage report from c8
------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files   |     100 |      100 |     100 |     100 |
 counter.ts |     100 |      100 |     100 |     100 |
------------|---------|----------|---------|---------|-------------------
```

Let’s say we add a new function in our _counter.js_:

```ts
// added another function
export function setupCounter(element) {
  element.addEventListener('click', () => {
    setCounter(counter++);
    element.innerHTML = `count is ${counter}`;
  });
}
```

If we run `vitest run --coverage`, we can see that not all our code has been tested:

```plain
$ vitest run --coverage

 RUN  v0.27.2 /tmp/vitest
      Coverage enabled with c8

 ✓ tests/counter.test.js (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  15:31:52
   Duration  2.03s (transform 480ms, setup 0ms, collect 40ms, tests 2ms)

 % Coverage report from c8
------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files   |   54.54 |      100 |      50 |   54.54 |
 counter.ts |   54.54 |      100 |      50 |   54.54 | 7-11
------------|---------|----------|---------|---------|-------------------
```

This report ensures that most of your project’s code is tested.

By default, Vitest uses the c8 package to run coverage reports. You may install it manually with the command:

```shell
npm i -D @vitest/coverage-c8
```

Or install it when prompted:

```plain
$ vitest run --coverage
 MISSING DEP  Can not find dependency '@vitest/coverage-c8'

 ✔ Do you want to install @vitest/coverage-c8? …
```

## Even more features. Component testing

Another common testing we can do with Vitest is component testing, which verifies the functionality of individual components.

Let’s add an _App.test.jsx_ to a React App. For this example, we’re using a non-Vite project (i.e. basic Create React App template) to show that Vitest works just as fine.

In this test, we want to test only the App component and here’s what our _App.test.jsx_ will look like:

```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import App from './App';

describe('App Component Test', () => {
  it ('renders without crashing', () => {
    const { container } = render(<App />);
    expect(container).toBeInTheDocument();
  })

  it('renders learn react link', () => {
    render(<App />);
    const linkElement = screen.getByText(/learn react/i);
    expect(linkElement).toBeInTheDocument();
  });
});
```

Now we run the tests with `npm test` or `npx vitest`. The 2 tests should pass successfully, as shown below:

```plain
$ npx vitest

 DEV  v0.27.2 /tmp/vitest

 ✓ tests/App.test.jsx (1)

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  16:12:12
   Duration  1.94s (transform 685ms, setup 126ms, collect 356ms, tests 37ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit
```

## Conclusion

In this article, we learned about Vitest, a fast and modern unit testing framework powered by Vite. Thanks for reading, I hope it has been a helpful article in getting you started with Vitest. Please refer to the References section below if you would like to read more about Vitest and Vite. Cheers!

## References

- [Getting started with Vitest](https://vitest.dev/guide/)
- [Test coverage](https://vitest.dev/guide/coverage.html)
- [Comparisons with other test runners](https://vitest.dev/guide/comparisons.html)
- [About Vite](https://vitejs.dev/)

_Originally published by Victoria Lo in [Uploadcare Blog](https://uploadcare.com/blog/vitest-unit-test-framework/)_