The Jared Wilcurt
Posted on May 26, 2023
Intro
The following is about a 5½ year old production web app with 1,256 real world unit tests across 139 test suites. This production app has been the primary daily job of ~5 engineers since it's inception. Building it out, updating it, adding/demoing features, etc. It's a rare, real-world example of a largish webapp.
Motivation to switch to Vitest
Due to a bug that has existed in Jest since 2020, my team at work had to decide to either, fix the bug in Jest's repo.... or switch over to Vitest which doesn't have the bug. And since Jest's repo would require someone on our team to deal with a large Yarn mono repo, and TypeScript, it seemed like it might be worth it to avoid going that route.
Notably, we were not concerned with performance. So these benchmarks are more just personal curiosity than anything. But still worth sharing.
Also worth a note, these benchmarks were ran on the "worst case scenario" machine (the one with the slowest results to emphasize the biggest difference). I will also mention the results on the best case scenario (our CI).
Converting over the smaller repo first
Before taking on the multi-week task of converting the larger repo over to Vitest, it made sense to do a sort of "trial run" on the smaller repo.
Smaller Repo Stats:
- Vue 2 + Vuex + Vue-Router + Vite + Element-UI
- App Routes: 6
- Test Suites: 14
- Unit tests: 147
- Snapshots: 64
- From: Jest + JSDOM
- To: Vitest + HappyDOM
- Added 3 new dependencies, removed 7
- Benchmarks on slowest machine:
-
npm run unit
(cold)- Jest - 35.79s
- Vitest - 21.31s
-
npm run unit
(warm)- Jest - 27.77s
- Vitest - 16.38s
-
npm run unit-coverage
- Jest - 25.39s
- Vitest - 18.75s
-
So as you can see, Vitest actually outperformed Jest in all scenarios on the smaller repo. However, I think this had a lot more to do with switching to HappyDOM than Vitest itself.
Bigger Repo Stats:
- Vue 2 + Vuex + Vue-Router + Vite
- App Routes: 73
- Test Suites: 139
- Unit tests: 1,256
- Snapshots: 517
- From: Jest + JSDOM
- To: Vitest + JSDOM
- Added 3 new dependencies, removed 3
- Benchmarks on slowest machine:
Tool | Coverage | Real-Time | Self-Time | Transform | Setup | Collect | Tests | Env | Prepare |
---|---|---|---|---|---|---|---|---|---|
Vitest | 320.90s | 9.71s | 82.14s | 442.08s | 325.19s | 232.20s | 56.45s | ||
Vitest | 318.71s | 9.73s | 83.86s | 447.80s | 295.65s | 240.87s | 57.98s | ||
Vitest | 332.02s | 327.54s | 8.97s | 82.09s | 455.03s | 328.10s | 241.90s | 56.88s | |
Vitest | 415.32s | 408.03s | 9.80s | 99.78s | 568.28s | 420.10s | 294.90s | 70.65s | |
Vitest | ✔️ | 297.17s | 9.47s | 115.97s | 638.88s | 538.10s | 370.47s | 96.27s | |
Vitest | ✔️ | 272.07s | 9.37s | 100.96s | 576.24s | 540.31s | 328.53s | 84.68s | |
Vitest | ✔️ | 293.33s | 287.45s | 11.15s | 111.67s | 624.03s | 551.18s | 348.24s | 92.34s |
Vitest | ✔️ | 272.23s | 265.44s | 9.17s | 102.19s | 580.97s | 491.47s | 320.93s | 83.75s |
Jest | 213.68s | 207.69s | |||||||
Jest | 201.76s | 191.56s | |||||||
Jest | ✔️ | 226.06s | 221.30s | ||||||
Jest | ✔️ | 273.54s | 267.91s | ||||||
Vitest --no-isolate | 115.94s | 12.73s | 6.48s | 53.88s | 264.15s | 7.65s | 2.61s | ||
Vitest --no-isolate | ✔️ | 242.30s | 10.53s | 13.89s | 121.78s | 532.12s | 33.38s | 109.14s |
Benchmark Results:
Note that "Real-time" was me timing the run with a stop watch and "Self-Time" is what the test runner reported as the total duration. The two are actually always within a few seconds of each other.
Average without coverage:
- Vitest = 322s (+37.8% time) on average 122 seconds slower
- Jest = 200s
Average with coverage:
- Vitest = 281s (+12.8% time) on average 36 seconds slower
- Jest = 245s
You'll note that this time around Vitest was actually a little slower than Jest. And very weirldy, Vitest with coverage reporting enabled is actually faster than with it off. I believe this is mostly due to not switching to HappyDOM. There were enough test failures that needed fixing that just swapping out Jest was enough work for ~2 weeks. So I decided to skip HappyDOM.
But how can we make it faster?
In my last Dev.to post, I documented many ways to make Vitest go faster:
Here are some of the things from that post that I tried, and the results:
-
--single-thread
and--no-threads
didn't work. Basically no test would even run. This may just be the first test failing and the rest choked, but I didn't want to bother to find out. - Both
css: false
anduseAtomics: true
actually made it slower - With
--no-isolate
it was 2.8x faster than vitest and 1.7x faster than Jest, but 19 tests failed (see table above). Some people report issues with watch mode when using--no-isolate
. So I decided to not pursue it any further. Once thevm
module that Vitest relies on supports ESM, or when the amazingly named Shadow Realms are added to JavaScript, we will likely get this performance boost for free without the downsides.
What about on CI?
All of our repo's are "branch protected" on GitHub. They must pass ESLint, SassLint, Build, Unit tests, and E2E tests.
When running on GitHub Actions (GHA), we see average times of:
- Jest = 128s
- Vitest = 138s
Because it's only a 10 second difference to run the full suite on GHA, and we primarily just run specific test suites when working locally. The difference in time is negligible. Also, we know that eventually the bottlenecks preventing Vitest from going faster will be removed and it will then start easily out-performing Jest.
So we are sticking with Vitest going forward. At least... once this 239 file PR with +10,486, -36,461 diff gets reviewed 😰.
Wait did you say Vue 2?
Yes, these projects are using Vue 2. Vue 3 did not exist in 2017/2018 when these projects were started. We have plans to convert the larger repo to Vue 3 by the end of the year. And we will be sticking with the superior Options API in all repos.
Though newer repos we've started use this boilerplate. I would recommend it to everyone, for every project.
Photo Credit:
Posted on May 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.