
Product news and testing tips.
Every Jest-to-Vitest comparison benchmarks the speed. The cost that actually bites is behavioural: `mockReset()` restores the original implementation instead of clearing it, `__mocks__` directories stop loading unless you call `vi.mock()`, `vi.mock()` factories must declare their default export, and hooks nest instead of running as a list. Each changes what your tests *do*, silently, without a config error. The good news is the part nobody mentions: Vitest's JSON reporter is Jest-compatible, so your reporting and your test history survive intact.
Key takeaways
- Speed benchmarks measure the easy half. Behavioural differences are what cost engineering days.
- `mockReset()` is inverted between the two — in Vitest it restores the original implementation rather than clearing to undefined.
- A `__mocks__` directory silently stops being auto-loaded; nothing errors, the real module just runs.
- Vitest's JSON reporter emits Jest-shaped `assertionResults` with a space-joined `fullName`, so test identity and history carry over.
- Measure your own delta on your own suite — published benchmarks are run on repositories that are not yours.
The Jest-to-Vitest migration is nearly always justified on speed, and nearly always costed as if the work were mechanical: swap jest.fn for vi.fn, add a config file, done. The mechanical part is real and it is the cheap part. The expensive part is a handful of behavioural differences that change what your tests do without producing a single error — a mock that resets to the opposite thing, a mock directory that quietly stops loading, hooks that run in a different order. Tests that fail after the migration are easy. Tests that still pass while testing something else are the cost.
This post is about that second category, plus the one thing the migration genuinely does not cost you: your reporting and your test history.
Has Vitest actually overtaken Jest?
On raw distribution, yes — and by more than the usual framing suggests. In the week of 5–11 September 2026, the npm registry’s own download API recorded 77,062,981 weekly downloads for vitest against 33,667,567 for jest — roughly 2.3 times as many.
That number deserves an immediate caveat, because download counts are not user counts. Vitest is a Vite-ecosystem package and gets pulled in transitively by tooling that a developer never chose directly, and CI installs inflate both figures. Treat it as evidence of reach, not of adoption.
The survey data is more conservative and more about sentiment. State of JS 2025, with 10,820 respondents, still describes Jest as holding “a comfortable spot as the most-used testing tool” while noting that Vitest “is climbing the ranks so fast that it wouldn’t be surprising to see it overtake it in the upcoming year, especially with Jest’s satisfaction ratio trending down.”
Either way the direction is settled, which is why the interesting question is no longer whether to migrate but what it costs.
The four differences that change results, not configuration
These are the ones worth budgeting for, because none of them announce themselves.
1. mockReset() is inverted
This is the most dangerous single difference. In Jest, mockReset() strips a mock back to an empty function returning undefined. In Vitest, it resets the mock to its original implementation.
The consequence is not a failure. It is a test that keeps passing while exercising real code it was written to stub out — a network call, a database write, a payment call. Nothing errors, the assertion still holds, and the test now guarantees something other than what its name claims.
Vitest 5 sharpened the surrounding behaviour further: clearMocks now defaults to true, so mocks are cleared between tests whether or not you asked.
2. __mocks__ directories stop being automatic
In Jest, a __mocks__ directory adjacent to node_modules is picked up automatically. Vitest’s migration guide is explicit that mocked modules there “are not loaded unless vi.mock() is called.”
Same silent-success shape as the first: the real module runs, the test passes, and the stub you believed was in place is not.
3. vi.mock() factories must declare their exports
Jest infers a default export from a mock factory. Vitest requires it explicitly — a factory returning an object needs its default key spelled out, or the importing module receives something it did not expect.
This one at least tends to fail loudly, which makes it the cheapest of the four.
4. Hooks nest instead of listing
Jest runs hooks as a flat list. Vitest nests them by default, and restoring Jest’s ordering requires sequence.hooks: 'list'. For suites with deep describe nesting and setup at several levels, the order in which your fixtures are built changes — which is the classic recipe for order-dependent failures that reproduce only sometimes.
Two smaller removals belong in the same budget: done callbacks are unsupported (convert to async/await or promises) and legacy fake timers are gone.
What actually changes, at a glance
| Concern | Jest | Vitest | Fails loudly? |
|---|---|---|---|
mockReset() | Resets to an empty fn returning undefined | Resets to the original implementation | No — silent |
__mocks__ directory | Auto-loaded | Ignored unless vi.mock() is called | No — silent |
vi.mock() factory | Default export inferred | default must be declared | Usually |
| Hook ordering | Flat list | Nested (sequence.hooks: 'list' restores) | No — order only |
Globals (describe, it) | Injected | Opt in via globals, or import | Yes |
| Done callbacks | Supported | Unsupported | Yes |
| Legacy fake timers | Supported | Unsupported | Yes |
| JSON reporter shape | assertionResults + fullName | Identical | n/a |
The pattern in the right-hand column is the actual finding. The differences that error are cheap — you fix them the day you hit them. The three that do not are the ones that turn a one-week migration into a quarter of intermittent, unexplained behaviour.
The part that costs nothing: your reporting
Here is the piece that almost no migration guide mentions, and it is genuinely good news.
Vitest’s JSON reporter emits the same structure Jest does: an assertionResults array where each entry carries ancestorTitles, title, status, duration, failureMessages and a space-joined fullName. It is not merely similar; it is close enough that a single parser reads both. Qualflare’s CLI does exactly that — --format vitest and --format jest resolve to the same implementation, because there is no difference worth branching on.
Why that matters more than it sounds: tooling identifies a test by its full name, so identity is what determines whether a test’s past belongs to it. A migration that changed the shape of that name would silently retire every test in your suite and create an identical set of brand-new ones with no history — no flake scores, no duration trends, nothing to compare a regression against. That is the cost we documented across every parser when a rename breaks a test’s identity.
Jest to Vitest does not do this. Your flaky-test history, your slowest-test rankings and your flake rate all survive the migration. Given how much else changes, that is worth knowing before you plan around losing it.
One caveat from the format itself: Vitest’s documented example shows a leading empty ancestor title, producing a fullName with a leading space. Harmless, but if you ever diff names between the two runners by hand, trim before comparing.
How to measure your own migration cost
Published benchmarks measure someone else’s repository. This takes an afternoon and measures yours:
- Inventory by test shape, not by file count. Count files that use module mocks, files that use fake timers, component tests, and plain unit tests. The four categories have wildly different conversion costs; a raw file count predicts nothing.
- Convert one file of each shape and time it honestly, including the debugging, not just the editing.
- Multiply out. Four numbers times four counts is a real estimate, and it will usually be larger than the one in your head.
- Run both runners against the same suite for a week. The CI-time delta on your tests is the only speed number that should influence the decision.
- Watch the pass rate, not just the failures. This is the step people skip. A migration that silently converts stubs into real calls shows up as tests that still pass — so compare flake rates and durations before and after, not just red counts. A test that got 40× slower after the migration is a test that started doing real I/O.
Step 5 is where having history across runs stops being a nice-to-have. Comparing a suite to its own past is the only way to notice that a green test changed behaviour, and it is the reason the reporting continuity above is worth more than it first appears. If CI time was the motivation for migrating, that same before-and-after comparison is also how you find out whether you got it.
Where Qualflare fits, plainly
Qualflare is a results and observability layer: it reads what your CI already produced and analyses it across runs. It does not run your tests, and it has no opinion about which runner you choose. What it does here is narrow and specific — because it parses Jest and Vitest output identically, a suite keeps its identity and its history through the migration, so the before-and-after comparison in step 5 is actually available to you. Send results with --format vitest or --format jest; the analysis is the same either way.
Frequently asked questions
Is Vitest actually faster than Jest?
Usually, and the reasons are structural rather than marginal: Vitest reuses Vite’s transform pipeline and native ESM handling instead of transpiling every module through Babel on each run. But the size of the win depends entirely on your suite. A suite dominated by slow integration tests hitting a database will barely move, because the bottleneck was never module transformation. Measure your own delta before treating speed as the reason to migrate.
What is the most dangerous difference when migrating from Jest to Vitest?
mockReset(). In Jest it resets a mock to an empty function returning undefined; Vitest resets it to the original implementation. That inversion does not error — it silently changes what your mocks do, so a test that was asserting against a stub starts exercising real code. Tests that still pass afterwards are the ones to worry about, because they are now testing something other than what they claim.
Do my __mocks__ directories keep working?
No, and this one fails quietly. Vitest’s own migration guide states that mocked modules in a root __mocks__ folder “are not loaded unless vi.mock() is called”. In Jest, a __mocks__ adjacent to node_modules is picked up automatically. After the migration the real module runs instead, so a test that intended to stub the network may now make a request and pass anyway.
Does migrating break my test reporting or lose test history?
No — this is the part that costs nothing. Vitest’s JSON reporter emits the same shape Jest does: an assertionResults array with ancestorTitles, title and a space-joined fullName. Because tools key a test’s identity on that full name, tests keep their identity across the migration and their history follows them. Qualflare reads both with the same parser for exactly this reason.
Do I need globals: true?
Only if you do not want to import describe, it and expect in every file. Jest injects them; Vitest does not by default. The catch worth knowing is indirect: Vitest’s guide notes that with globals disabled, “common libraries like testing-library will not run auto DOM cleanup”, so state can leak between tests and produce exactly the kind of order-dependent flakiness that is hardest to diagnose.
How do I estimate the migration cost before committing to it?
Convert one representative test file per pattern you use — a plain unit test, one with module mocks, one with fake timers, one component test — and time each conversion. Multiply by how many files of each shape you have. That number is far more useful than any published benchmark, because it measures your codebase rather than someone else’s demo repository.


