Vitest runs Unit Tests 50% Faster

In the last quarter I have been working on a project using Nextjs App Router. The experience has been great so far and eventually I needed to write unit tests to ensure my code works as expected when it evolves over time. In React world, Jest and Testing Library have been the testing libraries. So of course that was the first thing I tried. Long story short I started encountering some errors including:

  • Tests not running
  • Random errors about modules I wasn’t using
  • Unable to mock certain modules

This was when I thought I could try Vitest since it was a brand new project. For those who don’t know Vitest is a unit testing framework built on top of Vite with built in TypeScript and ESM support. It also has almost 1-to-1 API mapping to what Jest offers so the learning curve should be relatively flat.

Result

I will show you the steps I took to set up Vitest, but first I would like to show you the result.

With 58 tests, it takes around 15 seconds on Gitlab CI to finish with coverage 96.77 % in statements.

After I initially set up Vitest in the new project, I started migrating one of the existing projects that has over 1200 tests. Vitest was able to run through 1338 tests in 460 seconds with coverage 92 % in statements comparing to the 865 seconds previously with Jest.

image

Setup

Similar to Jest, we need a config file that instructs Jest what to do and a setup file that runs before we start running the actual tests. I just want to highlight couple things and you can refer to Vitest documentation for the rest of the config file.

In Jest you can actually use Jest naming to access the methods to do things like mocking your modules. In Vitest, by default you need to import those methods explicitly in each test file. You can, however, update your config file so you can access it globally like in Jest:

typescript
// vite.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true
  }
})

And in your tsconfig.json , you can add Vitest typings:

json
// tsconfig.json
{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

If somehow this doesn’t work you can add a vite.d.ts type file that includes:

json
/// <reference types="vitest/globals" />
/// <reference types="@testing-library/jest-dom" />

Then remember to update tsconfig.json to include that type:

json
// tsconfig.json
{
  "include": ["vite.d.ts"]
}

If you are using Testing Library like I was, you can also include jest-dom type in the same file.

Clean Up

In the vitest setup file, I make sure I clean up and clear all mocks after each test:

typescript
import { cleanup } from '@testing-library/react'

afterEach(() => {
  vi.clearAllMocks()
  cleanup()
})

Normally you probably don’t need to do this as Vitest by default spins up multiple workers using Tinypool to run tests in parallel and provide that isolation. However, we have found that such process is actually pretty slow in CI/CD environment. This is similar to why Jest suggests running Jest tests using runInBand flag. In Vitest we would run:

typescript
npx vitest run --no-threads

Now we need to ensure the test cases are not polluted with the previous test results so we need to clear DOM rendering and reset the mocks to make sure each test is running isolated from the other.

Happy DOM

happy-dom is a JavaScript implementation of a web browser to provide enough APIs so we can do component testing and rendering. It can also be used as an alternative to JSDOM which I suspect most people using React have it installed. I don't have a benchmark testing to comparing using JSDOM and Happy DOM but the GitHub repository provides some insights in that regard and it shows it is significantly faster than JSDOM. So to contribute to fast unit test I have set up, Happy DOM mostly likely does contribute to that effort.

typescript
npm install happy-dom -D
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'happy-dom'
  }
})

If you find that you are missing some matcher functions that you were used to using, you might need to run

shell
npm install @testing-library/jest-dom -D

And in your vitest setup file you need to add this line

typescript
import '@testing-library/jest-dom/vitest'

Vitest UI

image

Vitest has a separate package that provides a user interface for you to interact with your tests. From the UI you can see which snapshot tests need to be updated, which test failed that requires fixing, and you can run individual test with a button.

If you find this useful please share it on Twitter and let me know on twitter.