---
url: https://huldoser.github.io/test-inventory/frameworks/node.md
description: >-
  Scan node:test tests without running them, interpret the inventory, and check
  node:test syntax and limitations.
---

# node:test

This guide covers **node:test 22.0 or newer**. You need Node.js 22.12 or newer to run test-inventory and an
existing JavaScript or TypeScript test suite. The scanner reads your source without executing your tests or starting
a browser. These are minimum supported versions; [CI comparisons](/guide/runners#checked-in-ci) list tested versions.

[Choose another framework](/frameworks/) · [Use an AI client](/guide/agents)

## Install

```sh
npm install --save-dev test-inventory
```

## Scan your suite

Run from the folder containing your project's `package.json`:

```sh
npx test-inventory --framework node --pretty --output inventory.json
```

To select files explicitly, use quoted globs. This example matches the `pricing-service` fixture used below;
adjust the globs to your suite's layout:

```sh
npx test-inventory "test/**/*.js" "src/**/*.test.js" --framework node --output inventory.json
```

The command replaces `inventory.json` once the new output is complete. It does not change your test files.

## Check the result

Print the counts and diagnostics from the saved file:

```sh
node -e "const i = require('./inventory.json'); console.log(i.summary); console.log(i.diagnostics)"
```

For matching tests, `summary.fileCount` and `summary.testCount` should be greater than zero. Review diagnostics
even when the command exits with 0: errors can mean tests are missing. If the result is empty or unexpected,
use [troubleshooting](/guide/troubleshooting).

The inventory describes source declarations, not execution results. Conditional skips are not proof that a run
skipped the test. Loops and tables are one record by default; supported cases can be expanded with `--expand-cases`.
Expected-to-fail tests execute and check for failure; they are not disabled tests.

Tests created by unreadable helpers can be missing without a diagnostic. A dependency hash does not prove complete
coverage of runtime dependencies. Read the [shared limits](/guide/limits) before using scans to make CI decisions.

## Find skipped tests

```sh
npx test-inventory --framework node --state skip --fields fullName,state,condition,reason --pretty
```

This uses the same file selection as the first scan. If you supplied globs, `--root` or `--framework-version`,
repeat those options here.

This selects source declarations marked skip; it is not a report of a test run.
See [query recipes](/guide/recipes) for other inventory queries.

## Example

`pricing-service` tests a quote service with Node.js's own runner, `node:test`, run by `node --test` from its `test`
script. A test runs subtests with `t.test()`, and a subtest can skip itself.

```js excerpt
// test/quotes.test.js
test('reprices a basket', { timeout: 2_000 }, async (t) => {
  await t.test('reprices equities', () => {
  // ...
  await t.test('reprices bonds', async (bonds) => {
    if (!process.env.BOND_FEED) bonds.skip('no bond feed');
```

```sh
npx test-inventory "test/**/*.js" "src/**/*.test.js" --framework node --output inventory.json
```

Without the globs and `--framework`, the scan finds the same files from Node.js's default patterns, and node:test from
the `test` script. A subtest is listed under its parent's title, with the id of `reprices a basket` in `parentTestId`:

```json
{
  "parentTestId": "76080f80b7409d6f",
  "fullName": "reprices a basket > reprices bonds",
  "state": "skip",
  "isConditional": true,
  "condition": "!process.env.BOND_FEED",
  "reason": "no bond feed"
}
```

## Configuration and limits

Use `--framework node` for node:test. The scanner itself still needs Node.js 22.12 or newer.
There is no config file the scan reads: `node.config.json` holds no file selection and Node.js reads it only
with `--config-file`. File selection comes from supported `node --test` package scripts or explicit globs.

Script `--import`, `--require` and `--test-global-setup` files enter dependency hashes. They are not executed.
The inventory lists tests inside skipped suites; the runner may report only the enclosing skipped entry.
`isOnly` records the source modifier, while node:test applies it only with `--test-only`.

::: details Exact file selection

For node:test: the files the `node --test` commands of the `test` script give, with those of the scripts it runs through
`npm run`, `pnpm`, `yarn`, `bun run`, `npm-run-all`, `run-s` or `run-p`, or when it runs none, of the first script that
runs `node --test`, each a glob or a path, and from Node.js 26.8 a folder, for the default patterns inside it; without
any, Node.js's default patterns,
`**/*.test.*`, `**/*-test.*`, `**/*_test.*`, `**/test-*.*`, `**/test.*` and `**/test/**/*.*`, with the extensions `js`,
`mjs` and `cjs`, and from Node.js 22.18, which strips types by default, `ts`, `mts` and `cts`. Dot folders and
`node_modules` are left out. Scanned with globs, every file they match counts as run, as for the files given to
`node --test`.

:::

## Supported syntax

Node.js 22.0 and newer, the version the project pins; see [framework version](/guide/cli#framework-version).

### Where test comes from

| Form                       | Example                                                                                                                                                   |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Default import             | `import test from 'node:test'`, whose `test` also carries `describe`, `it`, `suite` and the hooks                                                         |
| Named import, also renamed | `import { describe, it, suite, test } from 'node:test'`                                                                                                   |
| `require()`                | `const test = require('node:test')`, whole, destructured or in place, as `require('node:test').it(...)`                                                   |
| Kept in a variable         | `const later = test.skip`: only what both branches of a choice share applies, also of one written in place, as `(process.env.CI ? test.skip : test)(...)` |
| Local helpers file         | `import { it } from './helpers'` or `require('./helpers')`, followed through local imports, and helper functions that declare tests, read at each call    |

node:test sets no globals: `it()` without an import is not a test.

### Calls

| Call                                                           | Read as                                                                                                                                                                                                                                                                     |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `test(name, options, fn)`, `it`, and `describe` or `suite`     | A test or describe. Each part may be left out: without a name, the test is named after its function, or `<anonymous>`; an object first is the options. A test without a function runs and passes.                                                                           |
| `.skip`, `.todo`, `.only`, from Node.js 24.14 `.expectFailure` | A skipped or todo test or describe, `isOnly`, or `expectedToFail`. Any other member, as `test.concurrent`, is an `invalid-chain` error: At file level Node.js fails the file; inside a describe it stops that callback, while earlier tests and tests outside it still run. |
| `{ skip }`, `{ todo }`                                         | A skipped or todo test or describe, with a string as its reason; only `false` and `undefined` set nothing, as in Node.js, where even `''` and `0` skip. A value the scan can't work out is a condition. `skip` wins over `todo`, and both over `expectFailure`.             |
| `{ only }`                                                     | `isOnly`. Under `node --test`, Node.js applies it only with `--test-only`.                                                                                                                                                                                                  |
| `{ timeout }`, `{ concurrency }`                               | `timeout`, and `isParallel` for the tests inside a describe or test whose `concurrency` is `true` or above 1.                                                                                                                                                               |
| `{ expectFailure }`                                            | From Node.js 24.14 and 25.5, `expectedToFail`; from 24.15 and 25.8 a string is its label, in `reason`.                                                                                                                                                                      |
| `{ tags }`                                                     | From Node.js 24.19 and 26.2, `tags`.                                                                                                                                                                                                                                        |
| `t.skip(reason)`, `t.todo(reason)`                             | On the test's context, its function's first parameter: the test skipped or todo. The body still runs, and so do its subtests.                                                                                                                                               |
| `t.test(...)`, or `test()` or `it()` called in a test's body   | A subtest: a test record with the titles of the tests around it in `suitePath` and the test that runs it in `parentTestId`. A subtest of a test skipped where it is declared is skipped too, as its body never runs.                                                        |
| `before`, `after`, `beforeEach`, `afterEach`                   | Hooks, counted as `beforeAll`, `afterAll`, `beforeEach` and `afterEach`.                                                                                                                                                                                                    |

Not read: `t.todo()` before a `t.test()` call, which makes the subtest todo too; `t.skip()` in a `beforeEach` hook,
which skips each test; `t.test()` in a helper function the test passes its context to; and a `describe` inside a test
body, which runs as a nested describe and gets a `nested-test` warning. A glob in a script is read as written, though
`sh`, which npm runs scripts with, reads an unquoted `**` as `*`; quote it, as Node.js expands it then.

The [shared syntax rules](/reference/syntax#in-every-runner) also apply.

## Next

* [Shared limits](/guide/limits): imports, generated tests, conditions and dependency coverage.
* [Key concepts](/guide/concepts): counts, states, IDs and hashes.
* [Output reference](/reference/output) and [diagnostics](/reference/diagnostics): interpret every field and warning.
* [CLI](/guide/cli), [library](/guide/library) and [MCP](/guide/agents): use the same inventory through each interface.
* [Compare with node:test](/guide/runners#node-test-s-list): differences from runner collection and execution reports.
