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

# Jest

This guide covers **Jest 29.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 jest --pretty --output inventory.json
```

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

```sh
npx test-inventory "src/**/*.test.js" "src/__tests__/*.js" "tests/**/*.test.js" --framework jest --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 jest --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

`settlement-service` settles trades with a custodian and is tested with Jest, in plain CommonJS. Its config splits the
tests into two projects:

```js excerpt
// jest.config.js
module.exports = {
  testTimeout: 10000,
  projects: [
    { displayName: 'unit', testMatch: ['<rootDir>/src/**/*.test.js', '<rootDir>/src/__tests__/**/*.js'] },
    { displayName: 'integration', testMatch: ['<rootDir>/tests/integration/**/*.test.js'] },
  ],
};
```

The integration tests retry, and tag the file for jest-runner-groups:

```js excerpt
// tests/integration/custodian.test.js
/**
 * @group integration/custodian
 */
const { settlementDate } = require('../../src/calendar');

jest.retryTimes(2);
// ...
describe('settlement instructions', () => {
  test('sends a delivery-versus-payment instruction', async () => {
    // ...
  }, 30_000);
```

```sh
npx test-inventory "src/**/*.test.js" "src/__tests__/*.js" "tests/**/*.test.js" --framework jest --output inventory.json
```

Without the globs and `--framework`, the scan finds the same files from the config, and Jest from `package.json`.
`jest.retryTimes()` applies to every test of the file, the `@group` is a tag, and the third argument is the timeout:

```json
{
  "fullName": "settlement instructions > sends a delivery-versus-payment instruction",
  "projects": ["integration"],
  "tags": ["integration/custodian"],
  "timeout": 30000,
  "timeoutSource": "test",
  "retries": 2,
  "retriesSource": "file"
}
```

A table of objects is one record with its `caseCount`, with the config's `testTimeout`; `--expand-cases` lists each
case, titled as Jest titles it:

```json
{
  "fullName": "netting > moves $cash in cash for a $side of $quantity at $price",
  "isParameterized": true,
  "caseCount": 2,
  "projects": ["unit"],
  "timeout": 10000,
  "timeoutSource": "config"
}
```

Other Jest examples: [Puppeteer on Jest](/frameworks/jest-examples#puppeteer-on-jest).

## Configuration and limits

The scan reads the root `jest.config.*`, otherwise `package.json`, plus configs named in `projects`.
From Jest 30, a string-valued `jest` key names a config file. Multiple configs in the same folder produce a
`config-value-unknown` warning because Jest refuses them. With no root config, Jest's defaults apply.
`injectGlobals` and supported run settings are read.

A config built by Next.js's `createJestConfig(config)` is not worked out. A package `preset` is not read;
if it may select files and neither `testMatch` nor `testRegex` is set explicitly, `config-value-unknown`
reports that Jest's defaults are used. A local preset path is read.

A config Jest refuses, such as one excluding `js` from `moduleFileExtensions` or naming nonexistent `roots`,
is read as written. Assigning its properties one by one, such as `module.exports.testMatch = [...]`, is not read.
`moduleNameMapper`, `modulePaths` and `moduleDirectories` are followed for imports and dependency hashes;
runtime mappings that cannot be worked out are left out.

`jest.setTimeout()` and `jest.retryTimes()` inside tests, hooks or functions are not read.
Tests registered by functions that setup files put on `globalThis`, or packages such as `babel-plugin-tester`,
can be missing without a diagnostic when the call does not look like a test.
Flow annotations cannot be parsed, even if Babel would compile them before Jest runs; those files get
`parse-error` and their tests are missing.

::: details Exact file selection

For Jest: the files under `roots` with an extension in `moduleFileExtensions` that `testMatch` or
`testRegex` match, without those `testPathIgnorePatterns` and `modulePathIgnorePatterns` leave out, with each version's
defaults and `<rootDir>` where Jest replaces it. Jest's `projects` takes folders, globs and config files, each with the
config Jest finds there, and objects, whose `rootDir` defaults to the config's folder; the config's own files then count
only when an entry names its folder, and an empty `projects` runs the config itself. A `preset` written as a path, such
as `'../../jest.preset.js'`, is read, with the config's own settings over it; one that is a package isn't.

:::

## Supported syntax

Jest 29.0 and newer.

### Where test comes from

| Form                       | Example                                                                                                                                                                                         |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Globals                    | `describe`, `it`, `test`, `fit`, `xit`, `xtest`, `fdescribe` and `xdescribe` used without an import or declaration                                                                              |
| Named import, also renamed | `import { test, it, describe, xit } from '@jest/globals'`                                                                                                                                       |
| The module                 | `import * as globals from '@jest/globals'`, a default import (with Babel or TypeScript), or `require('@jest/globals')`, whole, destructured or in place                                         |
| Kept in a variable         | `const onCi = process.env.CI ? test.skip : test`: only what both branches share applies, also to a choice written in place, as `(process.env.CI ? test.skip : test)(...)`                       |
| Local helpers file         | `import { it } from './helpers'` or `require('./helpers')`, followed through local imports, a global it exports, as `export const pendingTest = xit`, and functions that return a test function |

`fit` and `fdescribe` are `.only`; `xit`, `xtest` and `xdescribe` are `.skip`. As in Jest, a describe's own `.only` or
`.skip` wins over the one around it, a skipped describe skips every test in it, a todo or a `.only` too, and in a
`describe.only` that has a test with its own `.only`, its other tests are left out of focus. A file that uses the
globals while its config sets `injectGlobals: false` gets a `globals-not-enabled` warning.

### Calls

| Call                                                                               | Read as                                                                                                                                 |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `test(title, fn)`, `test(title, fn, timeout)`                                      | A test, with `timeout` when the third argument is a number the scan can work out.                                                       |
| `test.todo(title)`                                                                 | A todo. With more than a title, an `invalid-chain` error: Jest fails the file.                                                          |
| `test(title)`, `describe(title)` without a function                                | A `test-without-body` error: Jest fails the file.                                                                                       |
| `.only`, `.skip`, `.concurrent`, `.failing`, `.each`, on the paths Jest has        | `test.concurrent.only.each` exists, `test.only.concurrent` doesn't and is an `invalid-chain` error. `.failing` is `expectedToFail`.     |
| `.each(table)`, also as a tagged template                                          | One parameterized record, with `caseCount`; one record per case with `--expand-cases`, titled as jest-each titles it.                   |
| `describe`, `describe.only`, `describe.skip`, `describe.each`                      | A describe. An async callback is an `async-describe` error: Jest fails the file.                                                        |
| `test.each(table)(title)` without a function                                       | One test that always fails, as Jest registers, with a `test-without-body` warning; the file still loads.                                |
| `test(title, 42, fn)`, `describe(title, {}, fn)`                                   | A `test-without-body` error: Jest takes the function second, and fails the file.                                                        |
| A describe callback that returns a value, or a `describe.each` with an empty table | A `describe-throws` error: Jest fails the file. A callback that returns a promise is an `async-describe` error.                         |
| `jest.retryTimes(n)`                                                               | `retries` on every test of the file: the last call made while the file loads wins, also from inside a describe.                         |
| `jest.retryTimes(n, { entireDescribe: true })`                                     | From Jest 30.5, `retries` on the tests of the describe it is called in.                                                                 |
| `jest.retryTimes(n, { waitBeforeRetry })`                                          | From Jest 30.0, the wait between retries as `retryDelay`.                                                                               |
| `@group name` in a comment                                                         | A tag on every test in the file, as jest-runner-groups reads it: any line of the file that starts with `@group`, after a comment's `*`. |

Jest's `.each` titles take `%p`, `%s`, `%d`, `%i`, `%f`, `%j`, `%o`, `%O`, `%#`, from Jest 30 `%$`, and `$name.path` and
`$#` for tables of objects and tagged templates. A title that shows an object through `%s` or `%o` stays one record with
`--expand-cases`, as Node.js prints objects differently from one version to the next.

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 Jest](/guide/runners#jest-s-list): differences from runner collection and execution reports.
