Jest 62,362 (jestjs.io (https://jestjs.io/ 62,362 )), created at Facebook and part of the OpenJS Foundation since 2022, bundles runner, assertions, mocks, snapshots and coverage in one package. Vitest 102,989 (vitest.dev (https://vitest.dev/ 102,989 )) copies Jest's API almost name for name but runs your files through Vite 25,978 's transform pipeline, so TypeScript, JSX, CSS imports and the aliases in vite.config.js work in tests with no extra setup. The current releases are Jest 30.5 and Vitest 5.0 (September 2026), which requires Node.js 22.12 2,131 and Vite 6.4 or later. Choose Vitest for Vite projects and new code; keep Jest where a large suite already runs on it.
export function total(items, discount = 0) {
if (discount < 0 || discount > 1) {
throw new RangeError("discount must be between 0 and 1");
}
const sum = items.reduce((acc, item) => acc + item.price * item.qty, 0);
return Math.round(sum * (1 - discount) * 100) / 100;
}
export const checkout = (items, save) => save({ total: total(items) });Install with npm 2,036 install -D vitest, add "test": "vitest" to the scripts, and put tests next to the code in files ending .test.js or .spec.js. describe groups tests, test (alias it) defines one, expect asserts, and vi.fn() creates a mock function that records every call, so checkout can be tested without a real database.
import { describe, test, expect, vi } from "vitest";
import { total, checkout } from "./cart.js";
describe("cart", () => {
const items = [{ price: 19.99, qty: 2 }, { price: 5, qty: 1 }];
test("adds price times quantity", () => {
expect(total(items)).toBe(44.98);
});
test("applies a discount and rounds to cents", () => {
expect(total(items, 0.1)).toBe(40.48);
});
test("rejects a discount above 100%", () => {
expect(() => total(items, 1.5)).toThrow(RangeError);
});
test("checkout passes the total to the save callback", () => {
const save = vi.fn();
checkout(items, save);
expect(save).toHaveBeenCalledWith({ total: 44.98 });
});
});> npx vitest run --reporter=verbose
✓ src/cart.test.js > cart > adds price times quantity 2ms
✓ src/cart.test.js > cart > applies a discount and rounds to cents 0ms
✓ src/cart.test.js > cart > rejects a discount above 100% 0ms
✓ src/cart.test.js > cart > checkout passes the total to the save callback 2ms
Test Files 1 passed (1)
Tests 4 passed (4)
Duration 222ms (transform 44%, import 39%, worker 9%, tests 8%)Plain vitest starts watch mode and reruns only the tests affected by the file you save; vitest run runs once, as CI needs.
The same test in Jest
After npm install -D jest, the file changes in one line: import from @jest/globals and write jest.fn() instead of vi.fn() (Jest also provides these names as globals). Jest's ES module support is still experimental, so a "type": "module" project runs node --experimental-vm-modules node_modules/jest/bin/jest.js; CommonJS or Babel-compiled projects run plain jest. A failing test prints the expected and received values and points at the line:
FAIL jestdemo/float.test.js
● adds floating-point numbers
expect(received).toBe(expected) // Object.is equality
Expected: 0.3
Received: 0.30000000000000004
> 2 | expect(0.1 + 0.2).toBe(0.3);
| ^| Matcher | Passes when | Use for |
|---|---|---|
| toBe(4) | Object.is identical | Numbers, strings, same object |
| toEqual({a: 1}) | Recursively equal values | Objects and arrays |
| toBeCloseTo(0.3) | Equal to 2 decimal places | Floating-point results |
| toThrow(RangeError) | The function throws | Validation errors |
| await expect(p).rejects | A promise rejects | Async errors |
| toHaveBeenCalledWith(x) | A mock received those arguments | Callbacks, fetch |
Tests run in Node by default, where document does not exist. Add // @vitest-environment jsdom 21,691 to a test file (after npm install -D jsdom), or testEnvironment: "jsdom" with the jest-environment-jsdom package in Jest. jsdom and the faster happy-dom 4,671 implement the DOM in JavaScript but do no layout: getBoundingClientRect() returns zeros. For layout, focus and real events, use a browser runner (Karma and Web Test Runner).