From Jest to Vitest and back

Sergey Gultyayev
4 min readJul 20, 2024

--

Recently, I have remembered that there is a tool called Vitest, which supposedly should be blazing fast. Something like Vite vs Webpack. I spent a whole day to migrate and make everything work, figure how Vitest can be integrated with Angular. Just to discover that Jest is faster. Way faster.

Photo by paolo candelo on Unsplash

Migration to Vitest

In general, migration looks pretty much simple thanks to the Analog.js project which provides a plugin for Vitest that allows it to work with Angular in a way very similar to how it worked in Jest.

For that, you need to install the package:

npm install @analogjs/platform --save-dev

And run the schematics (for NX workspace replace ng with nx ):

ng g @analogjs/platform:setup-vitest --project [your-project-name]

After that, you can do a global replacement of jest for vi in *.spec.ts . Since the API is 99% the same, it will just work. It’s not as simple, however, with types (e.g. SpyInstance etc.). They have different names, and you will have to find analogs in Vitest yourself. In general, this is not a problem because Vitest does not run type checks by default, so your TypeScript complaints are for the IDE only (unless you opt in for type checking).

One key thing to remember here is that during Vitest setup your config will have globals: true which:
1. Enables the use of expect , it , etc. without explicit imports.
2. Enables you to run fakeAsync because under the hood the plugin monkey patches global APIs to work with Angular zones. This means that you cannot import describe and it from vitest and use fakeAsync inside them as imported functions are not patched and you will get errors.

Now, you should be able to run ng test , or simply vitest to run all tests. It’s quite possible that you may encounter some errors due to missing global variables (which you may have configured for Jest previously).

When I first migrated, I started having errors around it.each and fakeAsync. A little of Googling has shown that it’s a known bug in the Analog.js plugin for Vitest. This led me to contributing to the project to fix the errors that were stopping me from migrating. I was surprised by how fast my changes were accepted and released. Unseen speeds.

Another problem I encountered was segfault errors. Some time later I discovered that the issue was caused by the use of vmThreads for running tests in parallel. vmThreads , according to the Vitest docs, are experimental and prone to memory leaks. The reason why it was chosen as default in the plugin is to make the new environment closer to what it used to be with Jest + JSDOM (read more about it here).

To fix this issue, I had to change the config to use threads .

export default defineConfig(({ mode }) => {
return {
test: {
pool: 'threads', // add this property
},
};
});

Comparing performance

I compared the performance on a project with 500 tests. The tests are “classic” Angular production tests that avoid TestBed at all cost in favor of true unit tests. A few tests had to use TestBed to test effects as they require change detection to be triggered.

A discovery for me was that I did not need to create a component, or a host component (e.g. to test effects in services). Instead, set up TestBed to have all providers you need. Then, use TestBed.runInInjectionContext(() => { service = new Service() }) to create an instance of a service or a component that has access to the effects’ scheduler. When you need to trigger effects, use TestBed.flushEffects() . Easy peasy!.. I used to create a host component and call fixture.detectChanges() to trigger effects 🥲

When I ran the tests on my MacBook Pro on M1 chip with 32 Gb with Jest, it took approximately 60 seconds to run all 500 tests with no cache. With Vitest, it took 90 seconds. A 50% increase in execution time.

Things had gotten even worse in CI/CD. With Jest it took approximately 90 seconds to run all tests. With Vitest, it took 200! Moreover, our GitLab pipelines often killed the job, even if we requested 7 Gb of RAM (we did not use vmThreads by the time, so memory leaks shouldn’t have been the case). So, not only did it take drastically longer, but also we had to restart the job even more often.

Conclusion

It’s a little bit frustrating that spending almost a whole day brought performance degradations where improvements were expected.

Some say that the performance is worse because Vitest uses VM from Node.js, which is experimental now and has worse performance. And this may change in the future as it becomes more adopted and polished.

The migration from Jest to Vitest is very smooth already, and I expect it to become even smoother with time and adoption.

Also, one of the cool benefits I could see right away is that vi.mock (which works the same way as jest.mock ) mocks ES modules as effortlessly as Jest did with CommonJS (no need for nasty workarounds with dynamic imports to work in ESM).

To use Vitest or not is entirely up to you. Right now, I don’t see any benefits of using Vitest. The tool seems to be not as adopted in the Angular community, the performance is worse. The VM for mocking ES Modules in Node.js is still experimental and not optimized enough.

--

--

Sergey Gultyayev
Sergey Gultyayev

Written by Sergey Gultyayev

A front-end developer who uses Angular as a main framework and loves it

No responses yet