Skip to content

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 list tested versions.

Choose another framework · Use an AI client

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.

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 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 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:

Excerpt: js
// 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:

Excerpt: js
// 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.

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.

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 ​

FormExample
Globalsdescribe, it, test, fit, xit, xtest, fdescribe and xdescribe used without an import or declaration
Named import, also renamedimport { test, it, describe, xit } from '@jest/globals'
The moduleimport * as globals from '@jest/globals', a default import (with Babel or TypeScript), or require('@jest/globals'), whole, destructured or in place
Kept in a variableconst 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 fileimport { 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 ​

CallRead 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 functionA test-without-body error: Jest fails the file.
.only, .skip, .concurrent, .failing, .each, on the paths Jest hastest.concurrent.only.each exists, test.only.concurrent doesn't and is an invalid-chain error. .failing is expectedToFail.
.each(table), also as a tagged templateOne parameterized record, with caseCount; one record per case with --expand-cases, titled as jest-each titles it.
describe, describe.only, describe.skip, describe.eachA describe. An async callback is an async-describe error: Jest fails the file.
test.each(table)(title) without a functionOne 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 tableA 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 commentA 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 also apply.

Next ​

Documentation for test-inventory 0.10.1. Released under the MIT License.