jest review
Jest 30.4.2 is a Node test system that packages discovery, isolation, assertions, spies, module mocks, fake clocks, snapshots, coverage, watch mode, and parallel workers behind one CLI. That integrated model still suits established React, React Native, and Node suites, especially when their tests depend on Jest-specific mocks or serializers. Version 30.4 rewrites the custom module runtime, adds discovery through `--collect-tests`, accepts `.mts` and `.cts` for coverage, supports React 19 snapshot formatting, and exposes a worker shutdown timeout. Patch 30.4.2 fixes named imports from CommonJS modules whose exported function also owns properties. Our install confirms that convenience arrives with a large Node-only dependency graph.
Jest 30.4.2 took 16 seconds, installed 264 packages using 48 MB, printed 3 deprecation warnings, and returned 0 audit findings in our sandbox. Keep it where Jest-specific mocks and presets already pay that cost; start a new Vite-first project with Vitest unless a Jest-only integration decides the choice.
We installed it
| Install | ✓ · 16s | 264 packages on disk · 48 MB · 3 deprecation warnings |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does jest install cleanly?
Yes. In a fresh container with an empty cache, npm install jest finished in 16 seconds, leaving 264 packages and 48 MB on disk. npm audit reported no known vulnerabilities. The install printed 3 deprecation warnings.
Can jest run in a browser?
Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.
Does jest work with both ESM and CommonJS?
Yes. Both import 'jest' and require('jest') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does jest include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
jest or vitest: which should you use?
vitest: Use it for Vite-native transforms, ESM-first projects, and an API deliberately close to Jest's common test style. Jest 30.4.2 took 16 seconds, installed 264 packages using 48 MB, printed 3 deprecation warnings, and returned 0 audit findings in our sandbox.
When should you not use jest?
The application is built around Vite. Jest's own README says Vite's plugin system is not fully supported, while Vitest reads the Vite configuration directly.
Use it if
- An existing suite already relies on `jest.mock`, Jest snapshots, custom matchers, presets, or watch plugins.
- A Node or React project wants one tool family to supply assertions, mocks, fake timers, coverage, discovery, and reporting.
- CommonJS modules need deep replacement and call inspection through `jest.fn()` or `jest.spyOn()`.
- A monorepo needs multiple project configurations with one command and a combined report.
- The application is built around Vite. Jest's own README says Vite's plugin system is not fully supported, while Vitest reads the Vite configuration directly.
- Native ESM must work without experimental flags or a separate mocking sequence. Jest still labels ESM support experimental, and ESM mocks use `jest.unstable_mockModule()`.
- Running TypeScript tests must also prove their types. Babel strips TypeScript syntax but performs no type checking, so another command is required.
- A tiny Node package needs only a handful of assertions. Our clean install brought 264 packages and 48 MB before adding jsdom or a TypeScript adapter.
- Tests require browser layout, paint, or real navigation. jsdom is a separate simulated environment, and our attempt to bundle Jest for a browser failed.
Setup reality
We installed Jest 30.4.2 in a fresh Node 22 Bookworm container. npm needed 16 seconds, installed 264 packages, used 48 MB, and printed 3 deprecation warnings. npm audit found 0 known vulnerabilities. The top-level package declares 4 direct dependencies and 1 peer dependency and is 40 KB unpacked. Bundled types are present. Supported Node versions are ^18.14, ^20, ^22, and 24 or newer.
The package is CommonJS with an exports map; require() and ESM import both worked in our sandbox. An esbuild browser bundle failed because Jest is a Node CLI with filesystem and worker assumptions. DOM tests need jest-environment-jsdom installed separately and selected in configuration. jsdom gives tests browser-like globals, yet it cannot validate actual layout, painting, or all browser security behavior. Use Playwright or another browser runner for those claims.
Source transformation creates most setup mistakes. Babel can handle JavaScript, JSX, and TypeScript syntax, but its TypeScript preset removes types without checking them. Keep tsc --noEmit in CI or choose a transformer whose checking behavior you understand. ESM projects still follow Jest's experimental VM-module path and must register jest.unstable_mockModule() before dynamically importing the subject. The new jest.config.mts support only covers configuration parsing; it does not turn ESM mocking into CommonJS hoisting.
Parallel workers have isolated module registries, while ports, databases, files, and remote services remain shared. Allocate those resources per worker or cap maxWorkers when CI memory and service limits are tight. Restore spies and real timers after each test. Close servers and database pools in afterAll. --detectOpenHandles is useful for a hanging suite, but it adds enough overhead that it should stay a diagnostic command rather than the default test script.
Patterns
Test a pure function assert-sync-result
import { describe, expect, test } from '@jest/globals';
import { subtotal } from './subtotal.js';
describe('subtotal', () => {
test('adds prices', () => {
expect(subtotal([120, 80])).toBe(200);
});
});Explicit imports work when Jest globals are disabled and give TypeScript a clear source for the test API.
Wait for a rejected promise assert-async-rejection
test('rejects an expired session', async () => {
await expect(loadSession('expired')).rejects.toThrow('expired');
});Keep the `await`. Without it, the test may finish before the rejection matcher runs.
Exercise several input rows run-table-cases
test.each([
[0, 0, 0],
[4, 7, 11],
[-3, 3, 0],
])('add(%i, %i) returns %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});Each failed row prints its 3 values, which is clearer than a hand-written loop with one generic assertion location.
Record calls with jest.fn inspect-callback-calls
test('reports saved invoice IDs', () => {
const report = jest.fn();
saveInvoices([{ id: 12 }, { id: 19 }], report);
expect(report).toHaveBeenCalledTimes(2);
expect(report).toHaveBeenNthCalledWith(2, 19);
});Assert the call that carries behavior. A snapshot of the entire `mock.calls` array tends to preserve irrelevant details.
Replace a CommonJS module mock-commonjs-dependency
jest.mock('./mailer.js', () => ({
send: jest.fn().mockResolvedValue({ id: 'mail-1' }),
}));
const { send } = require('./mailer.js');
const { register } = require('./register.js');Jest hoists this CommonJS mock. Native ESM requires `jest.unstable_mockModule()` before a dynamic import.
Undo a Date.now spy restore-method-spy
afterEach(() => {
jest.restoreAllMocks();
});
test('uses the current timestamp', () => {
jest.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);
expect(makeStamp()).toBe('1700000000000');
});`restoreAllMocks()` reinstalls original methods. `clearAllMocks()` only clears history and leaves replacements active.
Run delayed promise work advance-fake-clock
afterEach(() => jest.useRealTimers());
test('retries after one second', async () => {
jest.useFakeTimers();
const result = retryOnce(fetchValue);
await jest.advanceTimersByTimeAsync(1_000);
await expect(result).resolves.toBe('ok');
});The async advance method lets queued promises settle between timer callbacks. Real timers must be restored for later tests.
Keep volatile data out of a snapshot snapshot-stable-fields
test('formats invoice lines', () => {
const invoice = buildInvoice(order);
expect({
currency: invoice.currency,
lines: invoice.lines,
total: invoice.total,
}).toMatchSnapshot();
});Exclude generated IDs and timestamps. Review the diff from `jest -u` instead of accepting a full replacement without inspection.
Close a test server manage-suite-resource
let server;
beforeAll(async () => {
server = await startTestServer();
});
afterAll(async () => {
await server.close();
});Files can run in separate workers, but TCP ports are shared. Allocate a unique port or serialize suites that use the same one.
Use jsdom for DOM APIs configure-dom-environment
// jest.config.mjs
export default {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
};Install `jest-environment-jsdom` separately. It simulates DOM APIs but does not calculate real browser layout or paint.
Count files with no tests enforce-coverage-floor
// jest.config.mjs
export default {
collectCoverageFrom: ['src/**/*.{js,ts}', '!src/**/*.d.ts'],
coverageThreshold: {
global: { lines: 85, branches: 80 },
},
};`collectCoverageFrom` keeps untouched source files in the denominator. Set the 85 and 80 values from your current baseline.
Reduce parallelism in CI cap-ci-workers
{
"scripts": {
"test:ci": "jest --maxWorkers=50% --ci"
}
}A 50% cap can lower memory and database contention. Measure elapsed time because fewer workers may lengthen the run.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| vitest | npm | Use it for Vite-native transforms, ESM-first projects, and an API deliberately close to Jest's common test style. |
| mocha | npm | Use it when the team wants a runner and prefers selecting assertions, spies, transpilation, and coverage independently. |
| ava | npm | Use it for isolated concurrent tests with explicit imports and a smaller, opinionated test API. |
More testing guides
pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

