5 good reasons to switch from Jest to Vitest

5 good reasons to switch from Jest to Vitest

Published:

Do you use Jest and do you wonder whether Vitest could be a good alternative? In this blog post, you will see why I made the switch and what to consider, if you think about switching as well.

Cover image

I often find that playing around with new tools and frameworks not only makes a lot of fun, but it can really improve our development experience.

When I started using Vitest about half a year ago, I quickly recognized that this library is one of those DX-improving tools and that I want to switch over from Jest .

Don’t get me wrong, Jest is a great library and I really appreciate all the work that went into it. I really enjoyed using Jest for about 5 years. Compared to Jasmine with Karma, it was a really nice performance boost: With jsdom , there was no need to run the unit tests in a real browser anymore.

But over time, tooling and standards have evolved. Now we have more and more ECMAScript modules instead of CommonJS modules and Vite with esbuild really speeds up the module transpilation.

What about performance?

When switching unit test runners, one important metric is performance. Not only the CI pipeline should be quick, but also the feedback loop of our unit and integration tests should be very fast. Otherwise, TDD is not really practical. A rerun of the tests related to a module that changed should ideally run in less than one second.

Hot Module Replacement

Vitest uses vite-node which enables Hot Module Replacement (HMR) within node. It uses esbuild , a very fast bundler written in go, to transpile your modules. For HMR, a module dependency graph is built, so if we change a module (i.e. change and save the corresponding file), the dependent tests can quickly and reliably be resolved and (re-)executed.

In Jest, file watching is implemented by utilizing git to more or less reliably determine which tests should be re-run. In the past, I had cases where unnecessarily many tests were executed, if I changed a source file. All in all, Vitest feels a lot faster than Jest, especially with large code bases.

Single run

Before switching to Vitest, I used SWC to transpile the tests.

The performance of SWC is comparable to that of esbuild. The overall execution time of a complete test run of Jest and Vitest is quite similar.

That said, there is an ongoing performance issue . In certain cases, Jest turns out to be 3 times faster than Vitest in a single run.

This issue seems to be related with the repeated import of heavy modules, such as jsdom. Using happy-dom instead is likely to fix this issue.

What’s the payload?

When evaluating whether you want to pick up a library, keeping the amount of transient dependencies small may be attractive because of those reasons:

  1. Having less disk space consumed by the project is not so relevant, but I still like to keep my node_modules directory small in size.
  2. There are fewer packages that can have security vulnerabilities and resolving those vulnerabilities can be quicker, if the library is actively maintained (which is the case for Vitest).
  3. The maintainers of the library tend to have more control over their code and are not limited by the APIs of other packages.

In the case of jest@29.5.0 versus vitest@0.29.8 , at the time of writing this post, we have about 197 packages of 32 MB when installing Jest from scratch. Vitest only brings in 62 packages of 26 MB.

If we happen to use vite as well, with the Jest setup we have 7 more packages to install and end up with a disk usage of 47 MB. Since Vitest already comes with vite as a dependency, there are no new dependencies added when installing vite.

Improved API

Arguably the most important aspect of a testing tool is how frictionless the development of tests is. Here, the API is an essential aspect. Even though, the API is very similar between the Jest and Vitest, I discovered some improvements.

No need for global scope pollution

When you use Jest, all testing framework functions and helpers are exposed to the global scope. You don’t have to import them inside your test suites.

However, if you are a fan of explicitly importing the stuff you need, Vitest has you covered. Naturally, you can also enable global exposure in the configuration.

Straight-forward restoration of mocks

One thing that really nagged us when using Jest was an unexpected behavior when restoring mocks.

If you do unit testing, you want to have your tests independent of one another. One important aspect is the call history of the mocks. There are three relevant functions in Jest and Vitest that can clean the call state of all your mocks: clearAllMocks() , resetAllMocks() and restoreAllMocks() .

clearAllMocks() simply resets the state. But it does not reset Behavior that was added within a test, i.e. via mock.returnValue() .

resetAllMocks() resets the state as well as any Behavior that is attached to the mock.

restoreAllMocks() resets the state and any Behavior that is not the default implementation.

Restoring is basically what I want to happen before each test. This way we will have the call state cleared and any attached behavior erased. But the default implementation is kept intact. Often, your test can become more DRY, if you attach some default implementations for your mocked functions.

Resetting all mocks would also destroy the behavior of smart mocks that have some default implementation attached.

The caution with restoreAllMocks() in Jest is that it only works on spies created with spyOn() .

So, in Jest we found ourselves only using clearAllMocks() to not demolish our shared smart mocks. Test suite specific mocks with default behavior we created in the beforeEach() callback of every test suite.

In Jest, as well as in Vitest, we also set the configuration option restoreMocks: true . This saved us a lot of explicit test suite setup callbacks.

Simple configuration

Configuring Jest can be quite a challenge. It comes with it own transformers API, whereas Vitest follows Vite’s configuration with sane defaults.

If you are using Vite, the configured plugins are used in Vitest as well. This way, you can have a shared configuration for development, production bundling and unit testing.

Sane defaults

If you are using TypeScript, for example, in Vitest you don’t have to configure anything to get started. Using Jest, you need to install ts-jest and maybe another transpiler like SWC for faster transpilation. Then you have to configure code transformations accordingly, because of Jest’s own transformation API.

Although it’s a small thing, I also prefer the CLI from Vitest. By default, watch mode is started, what often makes sense in a development context. You can opt out with the --run flag. If run on a CI environment, a single run is executed by default. In Jest, you always have to explicitly tell it to watch your files with the --watch flag.

Environment matching

In Jest, as well as in Vitest, you can have a single configuration for your tests for multiple configuration. I like having this for my monorepos. The downside is that you have to determine which environment your test should be running in.

If you set your default environment to node , you will have to overwrite the environment per test suite that should be running in a different environment like this: // @vitest-environment happy-dom .

But there is a feature, I really like in Vitest, that lets you get rid of this: You can define an environmentMatchGlobs property. This lets you specify an environment for random file sets.

Direct ESM support

Nowadays, ECMAScript Modules are very popular. This makes sense, since they are a fantastic standard. Yet, they are not supported by Node.js in a frictionless way. Vitest works very well with ESM, in Jest you have to transpile them first. But you can easily run into troubles, if you are using libraries that do not provide a CommonJS version. I encountered this problem with lit .

It seems that experimental ESM support is now available , but in the past it was one of the reasons, I switched to Vitest.

Do I regret having switched?

Those are the main reasons I switched to Vitest.

The migration from Jest to Vitest was straight-forward for the most part. There is also a great migration guide , if you decide to switch as well.

Also, the IDE integration of Vitest has matured, and I don’t see any drawbacks here compared to that of Jest.

If you are currently using jsdom, you should consider switching to happy-dom as well. There may be some little struggles with the differences in the API implementation, but for the most part it should be a no-brainer to switch those.

So, I’m saying goodbye to Jest and welcome to Vitest!

Maybe, if you haven’t already, you can now better tell whether you also want to do the switch.

Thanks for reading and have a nice day!