Why Vitest?
There are many test runners and testing frameworks available in the web ecosystem, but let's focus on those that are most relevant to Angular developers.
Since the deprecation of Karma in April 2023, the main alternatives in the Angular ecosystem are (by alphabetical order):
- Jest
- Vitest
- Web Test Runner (While promising, Web Test Runner is still in its early stages, so we will not consider it in this comparison.)
While both Jest and Vitest are mature and widely used in the web ecosystem, Jest has been massively adopted by the Angular community for many years. However, Vitest is gaining traction and is fixing many of the pain points that Angular developers have been facing with Jest.
Let's explore some of the reasons why you should consider using Vitest for your Angular projects.
๐ฆ ESM Supportโ
Vitest was designed with ECMAScript modules (ESM) compatibility in mind. This means that Vitest supports ESM out of the box. This is a big deal because Angular and the whole JavaScript ecosystem are moving towards ESM.
Beyond all the intrinsic advantages of ESM, this means that Vitest does not require any configuration to downgrade ESM to CommonJS, even for third-party libraries.
Haven't you ever been annoyed by the common SyntaxError: Unexpected token 'export'
that you get when you forget to downgrade ESM to CommonJS in Jest, and the weird negative regex in transformIgnorePatterns
?
- https://stackoverflow.com/questions/42260218/jest-setup-syntaxerror-unexpected-token-export
- https://stackoverflow.com/questions/49263429/jest-gives-an-error-syntaxerror-unexpected-token-export
- https://github.com/aws-amplify/amplify-js/issues/11435
- https://github.com/fullcalendar/fullcalendar/issues/7113
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'];
This also means that Jest "transform" phase will be slower than Vitest's because Jest will have to transform third-party libraries from ESM to CommonJS.
With Vitest, you won't have to deal with that anymore.
Isn't Jest also moving towards ESM?โ
Jest ESM support is still experimental as it relies on an experimental Node.js feature.
More precisely, Jest isolation model relies on Node.js V8 Virtual Machines (VM) where ESM support is still experimental.
๐๏ธ Jest vs. Vitest Isolation Modesโ
Jest Isolation Modeโ
Jest has only a single isolation mode: VM.
This means that Jest runs each test file in a separate Node.js VM. Theoretically, this is the best trade-off between isolation and performance. However, in practice, this comes with a cost:
- As mentioned before, ESM support in VMs is still experimental in NodeJS.
- Conceptual memory leaks as third-party libraries can be reloaded in each VM, thus causing memory leaks to amplify:
Vitest Isolation Modesโ
Conversely, Vitest offers multiple isolation modes:
- VM: Each test file runs in a separate VM. (just like Jest)
- Threads: Each test file runs in a separate thread.
- Forks: Each test file runs in a separate process.
- No Isolate: All test files run in the same pool of threads or forks.
This means that with Vitest you can where you want to put the cursor between isolation and performance.
In other words:
- for most Angular tests you can disable the isolation in Vitest to get the best performance. Angular's
TestBed
provides a sufficient isolation mechanism. - for tests that require a higher level of isolation, you can use
forks
isolation mode. This is the slowest but the most isolated mode. You can even make calls tochdir
as this will only change the current directory for the process of the test file where this call is performed.
TestBed
isolation mechanism and Angular's abstractions proved their efficiency with Karma. In fact, Karma runs all the tests in the same browser window.
โก๏ธ Performanceโ
When it comes to testing, the speed of feedback is crucial. You don't want to start multitasking while waiting for your tests to complete, or worse, to run them less frequently because they are too slow, right?
Please note that the following performance comparison is based on a "lab" situation and may not reflect your specific use case. You should always run your own benchmarks to make an informed decision.
This benchmark compares the performance of Jest with Vitest and its different isolation modes:
vitest-threads-no-isolate
: Vitest with threads and no isolation between different test files running in the same thread.vitest-vmforks
: Vitest with fork workers and VM isolation between different test files running in the same process.vitest-threads
: Vitest with thread workers running each test file in a separate thread.vitest-forks
: Vitest with fork workers running each test file in a separate process.
๐ป The source code of the benchmark is available here.
๐ The last benchmark run results are available here. (The disparity is even more significant when running the benchmark on Github Actions.)
Benchmark Cold Startโ
In this benchmark, Jest's and Angular's caches are cleared before each run to simulate a cold start.
Command | Mean [s] | Min [s] | Max [s] | Relative |
---|---|---|---|---|
๐ฅ vitest-threads-no-isolate | 2.925 ยฑ 0.084 | 2.801 | 3.039 | 1.00 |
๐ฅ vitest-vmforks | 4.761 ยฑ 0.172 | 4.565 | 5.116 | 1.63 ยฑ 0.08 |
๐ฅ vitest-threads | 7.795 ยฑ 0.356 | 7.549 | 8.771 | 2.67 ยฑ 0.14 |
jest | 8.282 ยฑ 0.253 | 7.995 | 8.641 | 2.83 ยฑ 0.12 |
vitest-forks | 9.362 ยฑ 0.259 | 8.999 | 9.792 | 3.20 ยฑ 0.13 |
Vitest can be up to 3x faster than Jest.
Benchmark Warm Startโ
In this benchmark, Jest's and Angular's caches are kept between runs to simulate a warm start.
Command | Mean [s] | Min [s] | Max [s] | Relative |
---|---|---|---|---|
๐ฅ vitest-threads-no-isolate | 2.945 ยฑ 0.048 | 2.906 | 3.075 | 1.00 |
๐ฅ vitest-vmforks | 4.768 ยฑ 0.092 | 4.594 | 4.962 | 1.62 ยฑ 0.04 |
๐ฅ jest | 4.775 ยฑ 0.119 | 4.627 | 5.065 | 1.62 ยฑ 0.05 |
vitest-threads | 7.581 ยฑ 0.201 | 7.299 | 7.908 | 2.57 ยฑ 0.08 |
vitest-forks | 9.172 ยฑ 0.131 | 8.930 | 9.385 | 3.11 ยฑ 0.07 |
angular-cli-web-test-runner | 9.980 ยฑ 0.074 | 9.828 | 10.095 | 3.39 ยฑ 0.06 |
During the warm start, Jest leverages its cache to speed up the test execution. In this case, Jest is as fast as Vitest with similar isolation mode but still 40% slower than Vitest without isolation.
As of today, Vitest does not have a cache mechanism, but the Angular vite plugin could leverage Angular's cache to speed up the test execution.
Watch Modeโ
Note that the current benchmark does not measure the watch
mode where Vitest is known to shine with 5x to 10x faster feedback than Jest.
This is decisive for Test-Driven Development (TDD). ๐
๐ Unified Configuration with Viteโ
Vitest can reuse the Vite configuration. This means that whenever something is fixed or improved for the Vite dev server, you will be able to benefit from it in Vitest as well. For instance, an official Angular plugin for Vite could partially reuse the Vite configuration for Vitest.
๐ฎ Vitest Is Evolving Fastโ
While the Jest team is still doing a great job, Vitest is evolving faster.
๐ป Enhanced APIโ
While Vitest covers the same features and APIs as Jest, making migration easier, Vitest also provides many additional APIs.
Here is a non-exhaustive list:
expect.soft
that comes in handy when making multiple assertions in a single test.expect.poll
andvi.waitFor
for retrying assertions. Note that retriability is one of the key features that made Cypress and Playwright successful.stubGlobal
andstubEnv
to override globals and env instead of handling them manually. (It is better to avoid this by providing test doubles at an abstraction level you control but that's another story we'll cover later.)- Fixtures just like Playwright.
๐ฑ Vitest is a Feature Buffetโ
Type Testingโ
Vitest supports type testing out of the box. This means that instead of compilation errors using TypeScript's @ts-expect-error
, you can write type tests for your complex generics, conditional types, and more using a similar API to your regular tests.
test('create a serializer for the provided type', () => {
const serializerFn = createSerializer<User>();
expectTypeOf(serializerFn).parameter(0).toEqualTypeOf<User>();
expectTypeOf(serializerFn).returns.toEqualTypeOf<string>();
});
This will also enhance your developer experience by providing an explicit Vitest test report for your type tests.
FAIL serializer.spec-d.ts > create a serializer with the provided type
TypeCheckError: Type 'string' does not satisfy the constraint '"Expected string, Actual void"'.
โฏ serializer.spec-d.ts:6:52
4| const serializerFn = createSerializer<User>();
5| expectTypeOf(serializerFn).parameter(0).toEqualTypeOf<User>();
6| expectTypeOf(serializerFn).returns.toEqualTypeOf<string>();
| ^
7| });
UI Modeโ
Vitest provides a UI mode to visualize your tests, their results, their execution time, and their code coverage.
Module Graphโ
What's even more compelling is that Vitest's UI provides a module graph to visualize the dependencies between the modules loaded by your tests.
Test Duration Reportโ
While this might seem futile, Vitest provides a detailed test duration report. This is very useful to quickly identify the performance bottleneck.
Duration 2.77s (transform 952ms, setup 195ms, collect 1.02s, tests 802ms, environment 390ms, prepare 38ms)
prepare
: time spent preparing the test runner (e.g. this will take much more time with theforks
isolation mode compared tono-isolate
mode).transform
: time spent transforming the test files.environment
: time spent setting up the test environment (e.g.happy-dom
orjsdom
setup).setup
: time spent executing the setup files.collect
: time spent collecting the tests (e.g. this includes the execution time of your test files code that is outside atest
).tests
: time spent executing the tests.
Note that the total duration is often lower than the sum of the parts due to parallelization.
Benchmarking (experimental)โ
Vitest also supports benchmarking out of the box. This means that you can write benchmarks for your functions and compare their performance over time.
Browser mode (experimental)โ
This is one of the latest sweets from the Vitest buffet. Vitest can run your tests in a real browser.
It is still experimental so it is still early to draw conclusions, but right now, we think that Playwright Component Testing approach is more suitable. We will elaborate on this in a future chapter.
๐ฏ Conclusionโ
It is time to consider Vitest for your Angular products. Vitest is a modern testing framework that is designed to address the common pain points of testing. It is faster, more flexible, and provides a better developer experience than Jest.
It is also widely used in the web ecosystem, so you can benefit from the community's feedback and contributions. By aligning with the web ecosystem and other frameworks, you can benefit from transferable skills and knowledge. Developers coming from other horizons will find it easier to contribute to your Angular products.
Note that Vitest shares most of the APIs with Jest, so the migration is generally smooth. Additionally, you can migrate progressively and keep using both Jest & Vitest during the migration period.
While this chapter highlights the advantages of Vitest over Jest, it is essential to acknowledge the outstanding work that the Jest team achieved over the years.
As a mature testing framework with over 10 years of evolution, Jest set a high standard and shaped the landscape of JavaScript testing.
However, as technology advances, so do our tools. At just three years old, Vitest has been built on top of modern technologies, learning from both the successes and limitations of its predecessors. Itโs a testament to Jestโs legacy that tools like Vitest are now pushing the boundaries even further.