Ben Holmes I'm a web dev, UX freak, and restless tinkerer. Let me teach you the art of building websites!

Testing Vite with minimal config using Vitest

7 min read 2010

Lightning Symbol Over a Reddish Purple Background

Vite’s been taking the world by storm since early 2021. It dramatically simplified developer tooling and became the underpinning of several popular metaframeworks. Now, it’s ready to take over our test suites via Vitest. 😮

Let’s discuss how Vitest works, compare it with a popular test suite configuration (Jest and Babel), explore the ways Vitest can simplify your test suite config, and examine how the performance gains can speed up your watch mode. If you’d like to jump around to sections of interest, click on the links below:

What is Vite?

Vite’s value proposition has become so broad that it’s hard to pin down. To summarize: Vite is a website bundler that can handle your JavaScript, your CSS, your static assets, and just about anything you load into an HTML document.

Remember Apple’s original value proposition for the iPhone, “An iPod, a phone, an Internet communicator…”? I see Vite as a similar proposition, as it bundles three related tools into one:

How does Vite pull this off? Well, instead of targeting a single main.js file as our website’s entry point, it crawls HTML files to track all JavaScript, styles, and miscellaneous assets you use. This enables Vite to manage the entire build pipeline, including a lightning-quick dev server. ⚡️

In development, Vite can intelligently decide what assets actually need to refresh when saving changes to a file. Edit a React component only used on your homepage, for example?

Instead of re-bundling your entire site as webpack might, Vite will send just the single React component over to your browser. It’ll even avoid refreshing the page and keep your state variables intact.

This hot module reloading is Vite’s secret sauce to a positive developer experience, which brings us to Vitest’s core value propositions.

What is Vitest?

🚨 Note: Vitest is still not recommended for production use as of February 2022. Check Vitest’s homepage for up-to-date suggestions before using it.

Vitest is a a Vi-testing framework built on top of Vite with an eye for both speed and minimal config. We’ll explore each of these in the following sections.

Vitest’s holistic approach to testing suites: Jest, Babel, and webpack all at once

Vitest’s approach to the testing space is similar to Vite’s approach on the bundling space: let the tool control your entire environment, top to bottom. Vitest is a replacement for a suite of tools:



  • Jest, Mocha, or Chai for test utilities
  • Babel for transpiling ESM, TypeScript, and more
  • webpack or Rollup for bundling test dependencies (if needed)

This means far less config, dependency mismatching, and performance monitoring for you to manage. Just install vitest and let the magic happen. ✨

Getting started with Vitest: How does it compare to Jest?

Despite Vitest’s sweeping value propositions, its test runner APIs are nearly identical to Jest across the board: describe, expect, it.each, mock functions, spies, concurrent flags for parallel tests… the gang’s all here (full list of APIs)!

In fact, Vitest and Jest are so similar that it’s hardly worth the code sample here. I suggest heading to Vitest’s playground to see the test runner in action. But for completeness, here’s a simple unit test written with Vitest’s APIs:

// example.test.js
import { expect, test } from 'vitest';

test('Math.sqrt()', () => {
  expect(Math.sqrt(4)).toBe(2);
  expect(Math.sqrt(144)).toBe(12);
  expect(Math.sqrt(2)).toBe(Math.SQRT2);
});

If you’re wondering, “Wait, why aren’t those test helpers globally available?” This is just the default. You can make the Vitest package globally available from a configuration file. See the Vitest docs for more information.

If you create this file locally, you can spin up Vitest’s watch mode by running npx vitest. You should see a single Math.sqrt() test with three passing assertions.

Building an example project for Vitest

Simple test cases are fine, but let’s explore some more complex use cases.

I created a full-stack web app using React for the frontend and a Netlify serverless function to handle form requests. I then wrote two test files:

You can explore the full GitHub repo here 👀

Note these each use a combination of not-so-stock tools: TypeScript, ESM for server and client-side logic, and JSX for the integration test. This should cover a spectrum of popular JavaScript use cases in a single project.


More great articles from LogRocket:


Let’s discuss the speed and config improvements using Jest + Babel as a baseline.

Minimal config with Vitest — from a Babel bonanza to one dependency

TL;DR: Vitest is much easier to configure from scratch than Jest and Babel. Using Vitest is almost a no-brainer if you use Vite already. Even if you don’t, there’s a minimal upfront cost to replicate your Babel build process in a Vite config. Still, if you want your config to be 100% identical to your web app’s, you might lean toward using Jest to piggyback on config.

Alright, let’s talk config. I tried to use the most popular test suite recommendations as a frame of reference: Jest for test utilities and Babel plugins to transpile what Jest doesn’t understand. I also avoided using Jest’s “experimental” flag for ESM because it’s rockier to use at this moment and may have performance implications.

Also, we’re configuring our test suite without considering the app itself. You might have a better time configuring Jest if your website already has a Babel and webpack config (say, CRA). Let’s ignore this for the sake of comparison, though.

Here are all the dev dependencies needed to get our tests running from absolute zero:

{
  "devDependencies": {
    // babel presets for ESM, typescript, and React
    "babel-jest": "^27.5.0",
    "@babel/core": "^7.17.0",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    // Jest types for global "describe," "it," etc
    "@types/jest": "^27.4.0",
    // Helper to stub out CSS modules when present
    // per Jest docs recommendation
    // https://jestjs.io/docs/webpack#mocking-css-modules
    "identity-obj-proxy": "^3.0.0",
    "jest": "^27.5.0",
    "typescript": "^4.4.4"
  }
}

That’s nine dependencies total. Despite using this combo of tools for years, it took nearly an hour of sifting through out-of-date packages and Stack Overflow posts to get here!

Stubbing out CSS modules was particularly thorny. Yes, your mileage will vary here, but I want to note this sticking point for newer devs, especially.

Now, let’s see our dependencies for Vitest:

{
  "devDependencies": {
    "@testing-library/react": "^12.1.2",
    "typescript": "^4.4.4",
    "vitest": "^0.2.7",
    // optional plugin - auto-inject "React" import when it's missing
    "@vitejs/plugin-react": "^1.0.7",
  }
}

We just went from using nine dependencies to four — three, excluding the optional React plugin. We also got it working on the first try from reading Vitest’s docs.

Now, if you’re thinking, “Well sure, but how much config did you have to write,” don’t worry. It gets better.

First, let’s see the necessary config for Jest plus Babel. We’ll need some Jest config to stub out CSS module imports:

// jest.config.js
module.exports = {
  coverageProvider: "v8",
  moduleNameMapper: {
    // stub out CSS imports per Jest's recommendation
    "\\.(css)$": "identity-obj-proxy",
  },
};

As well as a few Babel presets for React and TypeScript:

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
    ['@babel/preset-react', {
      "runtime": "automatic"
    }],
    '@babel/preset-typescript',
  ],
};

Now, let’s see the Vitest config. Because Vitest rolls up the testing and code bundling into a single tool, we only need one config file:

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

Look, there’s no config for TypeScript, CSS modules, environment presets, or the like! That’s because Vite handles this out of the box with sensible defaults that we don’t need to tweak. What’s more, because Vitest simply extends Vite’s capabilities, there’s zero necessary config for existing Vite projects.

Testing Vitest’s speed

I’ll add a disclaimer to this section before we begin: Vitest is still in its early days right now, so there are few benchmarks to speak of beyond this initial exploration from Matti Bar-Zeev. To summarize the findings:

  1. Running in watch mode is much faster than the Jest equivalent
  2. One-off runs, as you might use with automated test runners, were about the same speed

My findings were pretty dang similar.

Results from the example project

I will say this is purely anecdotal evidence. These results are based on my local machine’s performance and will likely vary for those cloning the repo and trying at home. I recommend skeptical early adopters try for themselves, but here are my findings:

  1. Vitest’s live reloading is just plain faster — about 39ms to rerun the suite on a file change versus Jest’s 250ms. The CLI’s console logs do lag a bit, though, making the performance boost more negligible at this smaller scale.
  2. Jest was nearly twice as fast running outside of watch mode — about 1s per run versus Vitest’s 1.88s using vitest run.

Let’s explore why this might be the case.

How is Vitest’s watch mode so quick?

I think Anthony Fu’s Twitter post says it best:

Just like how Vite works in the browser, Vitest also knows the graph of your modules, which makes it able to do smart detection and only rerun the related tests. Feels almost like HMR but for tests 😍

This is the greatest advantage of bundling your dev environment into a single tool. Because Vitest knows every module your app depends on, it can smartly decide which tests should rerun on a file change. This is especially useful in our integration test example.

Whenever we edit our Form.tsx, Vitest can quickly discover which tests rely on this file, re-process it in a flash, and only rerun the related tests. By comparison, I couldn’t get Jest to rerun at all while editing test file dependencies like this.

What work remains to speed up Vitest?

Okay, let’s address that performance hit outside of watch mode. This is a known issue in the Vitest community that stems from a core difference over Jest: Vitest is ESM-first, while Jest is based on the older CommonJS standard you may know from Node.

In other words, you can use import x from 'path' instead of const x = require('path') out of the box.

This is a huge win for modern developer tooling. As more and more modules are dropping CommonJS support, it’s crucial that our dev tools stay with the times.

However, this new ESM mindset forces Vitest to use a different test running strategy. To summarize:

  • Jest creates a “global context” for each test suite
  • Vitest can’t use a global module in the same way due to ESM support, so it creates isolated “workers” for each test file by default

This isolation means more time spent spinning up your test suites. Sadly, this has performance implications on small projects and at scale.

Note: you can disable this isolation from their CLI. You’ll need to be extra careful about global state shared between tests, but it should improve performance for most users.

But remember, Vitest is still in its early days. So, I’d avoid using Vitest for production apps right now, per their recommendation. Well, unless minimal config and fast dev servers are enough for you already. 😉

Conclusion

I hope this post gets you excited for Vitest’s future! It’s still in the development phase, so I highly encourage you to check out the Discord community to ask questions, contribute PRs, and watch the performance discussions over time. “Simplifying developer tooling” is a major trend to look out for, and I expect Vitest to only push this vision further.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Ben Holmes I'm a web dev, UX freak, and restless tinkerer. Let me teach you the art of building websites!

2 Replies to “Testing Vite with minimal config using Vitest”

  1. Note: Vitest is still not recommended for production use as of February 2022. Check Vitest’s homepage for up-to-date suggestions before using it.

    I can’t find this content. please link
    Can I use it in production now?

Leave a Reply