Jest and Vitest

Unit Testing with Jest and Vitest

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.

src/cart.js: the module under testJavaScript
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.

src/cart.test.js: values, an expected error and a mock callbackJavaScript
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 });
  });
});
Output
> 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:

Output of 56
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);
        |                     ^
The matchers you will use most often in Jest and Vitest
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).