Vitest 5 vs Jest 30: which JavaScript test runner to choose
Vitest 5 shipped in September 2026 and Jest 30 is on 30.5. Compare ESM, TypeScript, mocking, speed and migration effort, then pick a runner with confidence.
7 min read

In this article
For a new TypeScript or ESM project in October 2026, choose Vitest: it runs ES modules and TypeScript natively through Vite, and NestJS 12 now generates Vitest for ESM projects. Stay on Jest 30 if you have a large, stable CommonJS suite, rely on Jest-specific features such as automatic __mocks__ loading, or simply have no pain to fix. Both are actively maintained, and the test code itself looks almost identical.
This comparison uses the current releases, Vitest 5.0.3 and Jest 30.5.2 on npm, and the official release notes and migration guides for each.
The short comparison
| Vitest 5 | Jest 30 | |
|---|---|---|
| Released | 5.0 announced 3 September 2026 | 30.0 announced 4 June 2025; 30.5.2 current |
| Runtime requirements | Node.js 22.12.0 or later and Vite 6.4.0 or later | Node.js 18.x or later; TypeScript 5.4 or later for its type definitions |
| ES modules | Native, through Vite's module pipeline | Experimental; needs Node's --experimental-vm-modules flag |
| TypeScript | Transformed out of the box, no extra config | Via a transformer such as babel-jest or ts-jest |
Globals (describe, expect) | Imported from vitest, or enabled with globals: true | Available by default |
| Mock API | vi.fn, vi.mock, vi.spyOn | jest.fn, jest.mock, jest.spyOn |
| Browser testing | Browser Mode with real browsers, now with Trace View | jsdom environment (jsdom 26) |
| Config file | vitest.config.ts or your existing vite.config.ts | jest.config.js or jest.config.ts |
What changed in Vitest 5
The Vitest team describes performance as the main focus of 5.0. The release adds shared Vite servers for inline projects, makes the on-disk module cache a stable fsModuleCache option, adds a vitest doctor command that suggests faster configuration, and bundles Vitest's own dependencies to reduce install size. New APIs include vi.when for per-argument mock behaviour, a --repeats flag for hunting flaky tests, and fake timers that now mock Temporal by default.
Three breaking changes are worth knowing before you upgrade or adopt:
clearMocksnow defaults totrue, so mock call history is cleared before every test. SetclearMocks: falseto keep the old behaviour.- Unawaited async assertions now fail. A forgotten
awaitbeforeexpect(...).resolvesor.rejectsused to log a warning; in Vitest 5 it fails the test. - Test name patterns changed. Suite and test names are now joined with
' > 'rather than a single space, so a-tfilter that matched across the boundary Jest-style may stop matching.
What Jest 30 brought
Jest 30 was a large clean-up release. It dropped Node 14, 16, 19 and 21, upgraded jest-environment-jsdom from jsdom 21 to 26, and replaced module resolution with unrs-resolver, which the team says is more standards-compliant and faster. It also removed every alias matcher, so toBeCalled becomes toHaveBeenCalled and toThrowError becomes toThrow; the Jest team points to an eslint-plugin-jest autofixer for the rename.
The release post reports one large TypeScript application going from about 1,350 seconds and 7.8 GB peak memory to about 850 seconds and 1.8 GB. ESM support improved, with import.meta and native .mts and .cts handling, but the documentation still labels ESM support experimental and requires the --experimental-vm-modules flag. In ESM, module mocks use jest.unstable_mockModule followed by a dynamic await import(), because static imports are evaluated before jest.mock hoisting can apply.

The same test in both runners
Here is a small module that converts a price using an exchange-rate service, and a test that mocks the service. In Vitest:
// src/price.test.ts (Vitest)
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { priceInEur } from './price.js';
import { getRate } from './rates.js';
vi.mock('./rates.js', () => ({ getRate: vi.fn() }));
describe('priceInEur', () => {
beforeEach(() => {
vi.mocked(getRate).mockResolvedValue(0.9);
});
it('converts and rounds to cents', async () => {
await expect(priceInEur(10.99)).resolves.toBe(9.89);
expect(getRate).toHaveBeenCalledWith('USD', 'EUR');
});
});The Jest version for a CommonJS or Babel-transformed project differs only in where the API comes from:
// src/price.test.ts (Jest, globals enabled by default)
import { priceInEur } from './price';
import { getRate } from './rates';
jest.mock('./rates', () => ({ getRate: jest.fn() }));
describe('priceInEur', () => {
beforeEach(() => {
jest.mocked(getRate).mockResolvedValue(0.9);
});
it('converts and rounds to cents', async () => {
await expect(priceInEur(10.99)).resolves.toBe(9.89);
expect(getRate).toHaveBeenCalledWith('USD', 'EUR');
});
});Most suites migrate mechanically. The differences that cause real failures are in mocking semantics, covered next.
Mocking differences that break migrations
The Vitest migration guide lists these behaviour differences from Jest:
- Factory return values. In Jest, whatever a
jest.mockfactory returns becomes the default export. In Vitest the factory must return an object with each export named, so() => 'hello'becomes() => ({ default: 'hello' }). __mocks__folders are not applied automatically; Vitest uses them only when you callvi.mock(). Call it in a setup file to apply a mock everywhere.jest.requireActualbecomesawait vi.importActual(), which is asynchronous.mockResetdiffers. Jest replaces the implementation with one that returnsundefined; Vitest restores the original implementation passed tovi.fn(impl).- Legacy fake timers and
donecallbacks are not supported. Return a promise or useasyncinstead. - Hooks run as a stack by default in Vitest, and a
beforeEachmay return a teardown function. Setsequence.hooks: 'list'to match Jest's order. - Types such as
jest.Mockbecomeimport type { Mock } from 'vitest'.
// The mockReset difference, verified in both runners
const add = vi.fn((a: number, b: number) => a + b);
add.mockReturnValue(0);
add(1, 2); // 0
add.mockReset();
add(1, 2); // Vitest: 3 (original implementation)
// Jest: undefined
Which one should you choose?
Choose Vitest when
- The project is ESM-first, or already uses Vite for a front end.
- You want TypeScript tests without configuring a separate transformer.
- You want component tests in a real browser rather than jsdom.
- You are starting a NestJS 12 project as ESM: the NestJS v12 migration guide says generated ESM projects use Vitest, while CommonJS projects keep Jest.
Stay on Jest when
- You have a large CommonJS suite that is stable and fast enough.
- You depend on automatic
__mocks__,jest.requireActualpatterns or Jest-specific plugins and custom environments. - Your runtime is older than Node.js 22.12, which Vitest 5 does not support.
Speed is not a reliable tiebreaker in the abstract. Both projects publish impressive numbers for particular suites; what matters is how your tests use transforms, isolation and DOM environments. Run a representative slice of your suite in both before deciding.
A migration checklist from Jest to Vitest
- Install
vitestand add avitest.config.ts, or atestblock in your Vite config. Setenvironment: 'jsdom'or'happy-dom'for DOM tests. - Decide on globals. Either set
globals: trueand addvitest/globalsto your TypeScript types, or import fromvitestin each file. Note that Testing Library's automatic cleanup relies on globals. - Replace
jest.withvi.and fix mock factories that return a bare default export. - Replace
jest.requireActualwithawait vi.importActual, anddonecallbacks with promises. - Search for
mockResetand check whether tests relied on it returningundefined. - Move
__mocks__usage into explicitvi.mock()calls or a setup file. - Run the suite, then run it again with
--repeatson the tests that fail intermittently.
Gotchas
- Missing
awaitonresolvesandrejects. In Vitest 5 these now fail, which is correct but can surface dozens of latent bugs at once. - Relying on accumulated mock calls. With
clearMockson by default, a test that asserted call counts across tests will start failing. - Jest and ESM-only dependencies. The NestJS v12 guide warns that Jest can load its ESM-only packages only on Node.js 24.9 or later; the same pressure applies to other ESM-only libraries.
- Type checking. Vitest and
babel-jeststrip types without checking them, so a test run is not a type check. Keeptsc --noEmitin CI.
What to do next
If you are on Jest and happy, upgrade to the latest 30.x, run the alias autofixer, and revisit the question when ESM-only dependencies start to hurt. If you are starting fresh or already fighting --experimental-vm-modules, pilot Vitest 5 on one package, work through the checklist above, and migrate the rest once the pilot's CI times and flake rate look right.
Frequently asked questions
Is Vitest compatible with Jest?
The APIs are deliberately similar:
describe,it,expectand most matchers work the same way, andjest.fnmaps tovi.fn. Behaviour differs in places, notably mock factories,mockReset,__mocks__loading, hooks order anddonecallbacks.Does Jest 30 support ES modules?
Jest 30 improved ESM support, but its documentation still describes it as experimental. You must run Node with
--experimental-vm-modulesand usejest.unstable_mockModulewith dynamic imports for module mocks.What Node.js version does Vitest 5 need?
Vitest 5 requires Node.js 22.12.0 or later and Vite 6.4.0 or later.
Which test runner does NestJS 12 use?
According to the NestJS v11 to v12 migration guide, newly generated ESM projects use Vitest and CommonJS projects use Jest;
nest upgradebumps Jest to v30.Why did my Vitest tests start failing after upgrading to 5.0?
The most common causes are the new
clearMocks: truedefault, unawaitedresolvesorrejectsassertions that now fail, and test name filters affected by the new' > 'separator.Is Vitest faster than Jest?
Often, especially for TypeScript and ESM projects, but it depends on your suite. Benchmark a representative set of your own tests in both runners before migrating for speed alone.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- Announcing Vitest 5.0 — Vitest, 2026-09-03
- Migration Guide — Vitest
- Migrating from Jest — Vitest
- Jest 30: Faster, Leaner, Better — Jest, 2025-06-04
- From v29 to v30 — Jest
- ECMAScript Modules — Jest
- Migration guide (v11 to v12) — NestJS
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
More from Cloudsoft Zone

Development
WCAG 2.2 for developers: how to meet the six new A and AA rules
WCAG 2.2 adds six Level A and AA success criteria, from focus not obscured to accessible authentication. Here is what each requires and code that meets it.
By Cloudsoft Zone

TutorialBusiness Technology
Stripe Checkout in Node.js: verified webhooks and fulfilling each order once
Build a Stripe Checkout integration with stripe-node 23 and Express 5: create sessions on the server, verify webhook signatures against the raw body, and fulfil every order exactly once with a MySQL constraint.
By Cloudsoft Zone

TutorialNode.js
Node.js fetch in production: timeouts, retries and errors
Make Node.js fetch() production-ready: check res.ok, add AbortSignal timeouts, read every body, retry the right failures and tune undici connection pools.
By Cloudsoft Zone
