Back to BlogNode.js

Node.js Built-In Test Runner: Ditch Jest for Good in 2026

Node.js 22 LTS ships a mature built-in test runner. Zero deps, built-in mocking, coverage, and watch mode — here's everything you need to ditch Jest for pure Node.js projects.

Node.jsTestingJestJavaScriptnode:testTypeScript
Node.js Built-In Test Runner: Ditch Jest for Good in 2026

Let’s be honest — if you’ve been copy-pasting the same Jest config for years and wondering why your node_modules folder is 200MB for a simple Node.js app, there’s some genuinely good news. Node.js has shipped a built-in test runner since v18, and as of Node.js 22 LTS it’s production-ready. No installs. No config files. No plugins. Just write tests and run them.

What Is node:test?

The node:test module was introduced as experimental in Node.js 18 and became stable in Node.js 20. In Node.js 22 LTS — the current long-term support release — it’s feature-complete. Think of it as the test runner that’s been hiding in plain sight this whole time.

Here’s what you get out of the box, zero extra installs:

  • test() and describe()/it() syntax — works just like Jest

  • Built-in assertions via node:assert/strict

  • Mocking and spies with mock.fn() and mock.module()

  • Code coverage reports via --experimental-test-coverage

  • Watch mode with --watch

  • Test filtering with --test-name-pattern

  • TAP, spec, and dot reporters built in

Zero Config: Your First Test in 30 Seconds

Create a file called math.test.js and add this:

import { test } from 'node:test';
import assert from 'node:assert/strict';

test('adds two numbers', () => {
  assert.equal(2 + 2, 4);
});

test('throws on invalid JSON', () => {
  assert.throws(() => JSON.parse('not json'));
});

Run it: node --test math.test.js. Or run all test files in your project: node --test. No jest.config.js, no Babel transforms, no setup files. Just Node.

Grouping Tests with describe and it

The describe/it pattern is there if you prefer it over bare test() calls:

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

describe('UserService', () => {
  it('fetches a user by id', async () => {
    const user = await fetchUser(1);
    assert.equal(user.id, 1);
    assert.ok(user.name);
  });

  it('throws 404 for missing users', async () => {
    await assert.rejects(
      fetchUser(999),
      { message: 'Not found' }
    );
  });
});

Async tests work exactly as expected — just make the callback async. The runner awaits it automatically, no extra setup required.

Built-In Mocking (No jest.fn() Needed)

This is where most developers are surprised. node:test ships with a mocking API — function spies and module mocking, no sinon or jest-mock needed:

import { test, mock } from 'node:test';
import assert from 'node:assert/strict';

test('tracks function calls', () => {
  const double = mock.fn((x) => x * 2);

  double(5);
  double(10);

  assert.equal(double.mock.calls.length, 2);
  assert.equal(double.mock.calls[0].arguments[0], 5);
  assert.equal(double.mock.results[1].value, 20);

  mock.restoreAll();
});

Need to mock an entire module? Use mock.module(). It works with both CommonJS and ESM. The concept is the same as Jest’s jest.mock(), just with slightly different syntax.

Lifecycle Hooks: before, after, beforeEach, afterEach

All the lifecycle hooks you’re used to are here. This is great for setting up DB connections or seeding test data:

import { describe, it, before, after, beforeEach } from 'node:test';
import assert from 'node:assert/strict';

describe('Database tests', () => {
  let db;

  before(async () => { db = await connectToTestDb(); });
  after(async () => { await db.close(); });
  beforeEach(async () => { await db.seed(); });

  it('inserts a record', async () => {
    const result = await db.insert({ name: 'Alice' });
    assert.ok(result.id);
  });
});

Code Coverage, No Extra Tools Required

Forget installing nyc or c8. Node.js 22 has V8 coverage built in:

node --test --experimental-test-coverage

You’ll get a coverage table right in your terminal — line, branch, and function coverage per file. It’s not as polished as Istanbul’s HTML reports yet, but for CI pipelines and quick sanity checks, it’s more than enough.

Watch Mode for TDD Workflows

Run your tests in watch mode and they’ll re-execute on every file save:

node --test --watch

It’s not as smart as Vitest’s HMR-aware watch (which only reruns affected tests based on your import graph), but it covers the basic TDD loop perfectly. Pair it with --test-name-pattern to only run specific test cases:

node --test --watch --test-name-pattern "UserService"

TypeScript Support? One Flag Away

Node.js 22.6+ supports running TypeScript directly via --experimental-strip-types. Type annotations get stripped on the fly — no transpilation step, no ts-jest, no Babel:

node --experimental-strip-types --test **/*.test.ts

You’ll still want tsc --noEmit in your CI pipeline for actual type checking — strip-types is type-check-free by design. But for running tests fast, this combo is a genuine game changer.

When to Still Use Jest or Vitest

The built-in runner isn’t the right tool for every project. Reach for Jest or Vitest when you need:

  • Snapshot testing — node:test has no snapshot support (yet)

  • React/DOM testing — you need jsdom or happy-dom, which Jest and Vitest provide out of the box

  • Large existing test suites — migration overhead might not be worth it unless you’re already refactoring

  • Advanced parallel isolation — Vitest’s worker-based test isolation is more mature and flexible

If you’re building a pure Node.js API, CLI tool, or JavaScript/TypeScript library — the built-in runner covers you completely and shaves hundreds of megabytes off your dependencies.

TL;DR

  • node:test is stable in Node.js 20+ and feature-complete in Node.js 22 LTS

  • Zero dependencies, zero config — run node --test to get started immediately

  • Includes mocking, lifecycle hooks, coverage, watch mode, and test filtering

  • TypeScript support via --experimental-strip-types — no extra tools needed

  • Best for Node.js APIs, CLIs, and libraries — keep Jest/Vitest for React and snapshot-heavy suites