Vitest Configuration and JSON Reporter Output: Complete Reference
Last updated:
Vitest's config lives in a TypeScript file — vitest.config.ts— but the runner's relationship with JSON is everywhere around it: the JSON test-result reporter that CI dashboards consume, the coverage JSON files that gating scripts parse, the snapshot serializers that decide how objects render in .snap files, and the package.jsonscripts that orchestrate the whole thing. This page covers the config object end-to-end, then walks through every JSON surface Vitest 3 exposes — what each reporter emits, how to wire it into CI, and where Vitest's output overlaps and diverges from Jest's. For pure JSON syntax help on the files you write or parse along the way, jump to Jsonic's JSON Validator.
Parsing Vitest JSON output and the file looks malformed? Paste it into Jsonic's JSON Validator — it pinpoints truncated objects, escape errors, and bracket mismatches with exact line numbers.
Validate Vitest JSON outputvitest.config.ts: the core test config object
vitest.config.ts exports a default Vite config with an added testfield. Vitest 3 reuses Vite's plugin system, resolver, and transformer pipeline, so anything that works in vite.config.ts works here. The minimum config is roughly four lines:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'node',
include: ['src/**/*.test.ts'],
},
})The fields under test are independent — set only what you need to override. The defaults are sensible: environment: 'node', include globs that match common test patterns, JSDOM available on demand, ESM-first module resolution.
environment— runtime per test file (node,jsdom,happy-dom,edge-runtime). Override per file with a top-of-file comment:// @vitest-environment jsdom.include/exclude— glob arrays controlling which files Vitest treats as test files. Defaults excludenode_modules,dist, and build output.globals— whentrue, Vitest exposesdescribe,it,expectas globals (Jest-compatible). Default isfalse— import them explicitly fromvitest.setupFiles— string array of files to run before each test file. The right place for global mocks, jest-dom matcher imports, MSW server boot.reporters— array of reporter names or instances. Multiple reporters run in parallel and each routes to its own destination.
package.json scripts integration: test, test:ci, test:coverage
Vitest itself reads no config from package.json — but the npm/pnpm/yarn scripts that invoke it usually live there, and the script names you pick set the ergonomics of the whole project. The conventional set is three scripts: one for local watch-mode development, one for one-shot CI runs with JSON output, and one that adds coverage. See our package.json scripts guide for the broader scripts-field reference.
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ci": "vitest run --reporter=verbose --reporter=json --outputFile=test-results.json",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui",
"test:watch": "vitest watch"
},
"devDependencies": {
"vitest": "^3.0.0",
"@vitest/coverage-v8": "^3.0.0",
"@vitest/ui": "^3.0.0"
}
}vitest with no subcommand defaults to watch mode — convenient locally, wrong for CI. vitest run forces a single one-shot execution and exits with a non-zero code on failure, which is what you want in pipelines. Adding --reporter=verbose --reporter=json gives you both a human-readable terminal log and a structured JSON file in the same invocation.
For monorepos using Turborepo or Nx, wire the script up in the workspace task graph. See our Nx config guide for how the target.test.outputsfield should include the coverage and result files so Nx's remote cache hits work correctly.
JSON reporter: structured CI output for test results
The JSON reporter is the bridge between Vitest and everything downstream — CI dashboards, Slack notifiers, GitHub status checks, custom analytics. Enable it by listing 'json' in test.reporters and giving it an outputFile so the content lands on disk rather than stdout.
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
reporters: ['verbose', 'json'],
outputFile: {
json: './test-results.json',
},
},
})The emitted JSON follows the Jest schema for compatibility. A typical run looks like this (trimmed):
{
"numTotalTestSuites": 12,
"numPassedTestSuites": 11,
"numFailedTestSuites": 1,
"numTotalTests": 87,
"numPassedTests": 85,
"numFailedTests": 2,
"numPendingTests": 0,
"success": false,
"startTime": 1716480000000,
"testResults": [
{
"name": "/repo/src/api/users.test.ts",
"status": "failed",
"startTime": 1716480000123,
"endTime": 1716480000456,
"assertionResults": [
{
"fullName": "users API returns 200 for valid id",
"title": "returns 200 for valid id",
"status": "passed",
"duration": 14
},
{
"fullName": "users API rejects invalid id",
"title": "rejects invalid id",
"status": "failed",
"duration": 22,
"failureMessages": ["AssertionError: expected 500 to be 400"]
}
]
}
]
}For broader patterns around emitting structured CI artifacts, see JSON in CI/CD.
Coverage reporters: json, json-summary, lcov
Vitest's coverage subsystem is separate from the test-result reporter. It runs under --coverage and uses its own reporter list. The five common outputs are text (terminal table), html (browsable report), json (full Istanbul object), json-summary (compact per-file totals), and lcov (legacy format consumed by Codecov, Coveralls, and SonarQube).
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'json', 'json-summary', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
thresholds: {
statements: 80,
branches: 75,
functions: 80,
lines: 80,
},
},
},
})The json-summary output is the easiest to consume in a CI gate. It is one short JSON file with a total key plus one key per file, each carrying statements, branches, functions, and lines objects with pct fields. A 10-line shell script can read it and fail the build below threshold.
coverage.thresholds is the in-config equivalent — Vitest itself enforces the percentages and exits non-zero when any metric falls short, so most projects do not need a separate gating script.
Snapshot serializer JSON output and inline snapshots
Snapshots are not JSON files on disk — they live in .snap files with a custom format — but the inputs to snapshots are JSON-compatible values, and controlling how those values serialize is a JSON-shaping concern. Vitest uses the same pretty-format plugin protocol as Jest, so existing serializers port over without changes.
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
snapshotSerializers: [
'./test/serializers/big-decimal.ts',
'./test/serializers/iso-date.ts',
],
snapshotFormat: {
printBasicPrototype: false,
escapeString: false,
},
},
})A serializer module exports a test function (return true for values you want to handle) and a serialize function that returns a string. For inline snapshots — expect(x).toMatchInlineSnapshot() — the same serializer pipeline runs, and the formatted string is written back into the test file. snapshotFormat controls the global formatting knobs (indentation, prototype printing, max depth).
For broader testing patterns including snapshot strategy, see our JSON testing with Jest and Vitest guide.
Workspaces: vitest.workspace.ts for monorepo testing
Monorepos with multiple test surfaces — unit tests in node, component tests in jsdom, integration tests against a real database — used to ship a vitest.workspace.ts file at the repo root with an array of config paths. Vitest 3 introduces native projects that subsume this; the workspace file still works for backward compatibility.
// vitest.config.ts (Vitest 3 projects)
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
environment: 'node',
include: ['src/**/*.test.ts'],
},
},
{
test: {
name: 'dom',
environment: 'jsdom',
include: ['src/**/*.dom.test.tsx'],
setupFiles: ['./test/setup-dom.ts'],
},
},
{
test: {
name: 'integration',
environment: 'node',
include: ['tests/integration/**/*.test.ts'],
testTimeout: 30000,
},
},
],
},
})Each project runs with its own environment, include globs, setup files, and timeouts, but all share one runner invocation, one watch process, and one merged JSON output. To target one project: vitest run --project unit. To run multiple: vitest run --project unit --project dom. The merged JSON result file tags each entry with its project name so downstream parsers can split by suite.
Pool, threads, and timeout settings
Vitest 3 runs test files in isolated workers by default. The pool field picks the worker model, and pool-specific options under poolOptions tune concurrency. The defaults work for most projects; override when tests share state, hit shared resources, or need a hard parallelism cap.
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
pool: 'threads',
poolOptions: {
threads: {
singleThread: false,
maxThreads: 4,
minThreads: 1,
isolate: true,
},
},
testTimeout: 10000,
hookTimeout: 10000,
teardownTimeout: 5000,
fileParallelism: true,
sequence: {
shuffle: false,
concurrent: false,
},
},
})pool: 'threads'— Node Worker Threads. Fast startup, shares no memory with the parent. Default for most workloads.pool: 'forks'— child processes. Heavier, but tests that import native addons or rely on process-level globals need it.pool: 'vmThreads'— Worker Threads with VM contexts. Lower memory than full forks, fuller isolation than plain threads.testTimeout— per-test cap in ms. Override per test withit(name, fn, { timeout: 30000 }).fileParallelism— whether to run multiple test files concurrently. Set tofalsefor suites that touch a shared database.
Vitest UI, coverage thresholds, and CI integration
The Vitest UI (vitest --ui) is a browser-based dashboard served by the runner — useful locally for navigating large test trees. The package ships separately as @vitest/ui. In CI, what matters more is the combination of vitest run, the JSON reporter, and coverage.thresholds gating. A typical GitHub Actions step looks like:
# .github/workflows/test.yml (excerpt)
- name: Run tests with JSON reporter and coverage
run: pnpm test:ci -- --coverage
- name: Check pass/fail and surface failures
if: always()
run: |
jq '.success' test-results.json
jq '.testResults[].assertionResults[]
| select(.status=="failed")
| {file: .ancestorTitles[0], name: .title, message: .failureMessages[0]}' test-results.json
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.infoThe jq filter on the second step prints any failing test as a small object with file, name, and the first failure message — easy to read in CI logs and easy to forward to Slack with a one-line transform. Coverage uploads typically use lcov for legacy tools or coverage-summary.json for custom dashboards.
Vitest's coverage.thresholds.autoUpdate field is a useful escape hatch for legacy codebases: when true, Vitest writes the current coverage values back into the config as new minimum thresholds after each green run, ratcheting the bar upward without manual edits. Pair this with ESLint config conventions to keep both quality gates moving in the same direction.
Key terms
- vitest.config.ts
- The TypeScript file at the repo root that exports the Vitest config object. Built on top of
defineConfigfromvitest/config; reuses Vite's plugin and resolver pipeline. Vitest does not read config frompackage.json. - JSON reporter
- The
--reporter=jsonoutput that emits a Jest-compatible test-result object —numTotalTests,numPassedTests,testResults[]with nestedassertionResults[]. The format is stable across Vitest 1, 2, and 3. - json-summary
- A coverage reporter (not a test-result reporter) that writes
coverage-summary.json— compact per-file plus total percentages for statements, branches, functions, and lines. The lighter sibling of the fulljsoncoverage reporter. - projects (Vitest 3)
- Native multi-project mode declared inside
test.projectsinvitest.config.ts. Replaces the oldervitest.workspace.tsfile for splitting unit, DOM, and integration suites under one runner. - snapshot serializer
- A module exporting
testandserializefunctions that controls how a value renders in a.snapfile. Vitest uses thepretty-formatplugin format from Jest, so existing serializers port over unchanged. - pool
- The worker model Vitest uses to run test files in isolation —
threads(Worker Threads),forks(child processes), orvmThreads(Worker Threads with VM contexts). Picks the tradeoff between speed and isolation strength.
Frequently asked questions
What is the JSON reporter format in Vitest?
The Vitest JSON reporter (--reporter=json) emits a single JSON object describing the full test run. The top-level shape mirrors the Jest format for compatibility: numTotalTestSuites, numPassedTests, numFailedTests, numPendingTests, numTotalTests, startTime, success (boolean), and a testResults array. Each entry in testResults represents one test file and contains its absolute path, status (passed/failed), startTime, endTime, and an assertionResults array with one object per individual it() or test() call. Each assertion carries fullName, title, status, duration, and a failureMessages array (populated only on failure). The format is stable across Vitest 1, 2, and 3, which is why most CI dashboards and reporting tools that already parse Jest JSON output also accept Vitest output without changes.
How do I export coverage data as JSON for CI?
Set coverage.reporter to include either json or json-summary in vitest.config.ts, then run vitest with --coverage. The json reporter writes coverage-final.json — the full Istanbul coverage object with statement, branch, function, and line counts for every file. The json-summary reporter writes coverage-summary.json — a compact per-file plus total summary with pct fields (percent covered) for each metric. For CI gating, json-summary is the lighter, easier-to-parse option; for archiving and downstream tools like Codecov or Coveralls, the full json file is what you want. Both files land in coverage/ by default; override with coverage.reportsDirectory. You can list multiple reporters in the same run: reporter: ['text', 'html', 'json', 'json-summary', 'lcov'] produces all five outputs in one pass.
Can I configure Vitest in package.json instead of vitest.config.ts?
Vitest does not have a dedicated package.json field the way Jest does (Jest reads a "jest" key). The config must live in a Vite or Vitest config file: vitest.config.ts, vitest.config.js, vite.config.ts, or vite.config.js. If your project already has a vite.config.ts, you can add a test field to the existing defineConfig call and skip the separate file — that is the typical pattern for Vite apps. What you can put in package.json is the script integration: the scripts.test, scripts.test:ci, and scripts.test:coverage entries that wrap CLI flags. You can also use the resolve.alias and define fields in package.json via Vite plugins, but the test runner itself reads from the config file. For environment-specific overrides without a second config file, use the --config CLI flag or the VITEST_CONFIG environment variable.
How do I parse Vitest JSON output for a custom dashboard?
Run vitest with --reporter=json --outputFile=test-results.json so the JSON lands on disk rather than mixed into stdout. From there, parse it with any JSON library — jq for shell pipelines, JSON.parse for Node scripts, json module for Python. The fields you most often want are success (overall pass/fail), numFailedTests, and testResults[].assertionResults[].failureMessages for the actual failure strings. A common pipeline is: run tests with the JSON reporter, parse the file in a small post-step script, transform it into the schema your dashboard expects (often a flat list of {file, name, status, duration, message}), and POST that to an internal endpoint. Combine with --reporter=verbose if you also want human-readable terminal output alongside the JSON — Vitest accepts multiple reporters and routes them independently.
What's the difference between --reporter=json and --reporter=json-summary?
These two flags target different concerns. --reporter=json is a test-result reporter — it emits the full structured result of the test run (all suites, all assertions, all failure messages) in a single JSON object. It is what you pipe to dashboards, archival, or post-processing scripts. --reporter=json-summary, on the other hand, is a coverage reporter and only fires when --coverage is also passed; it produces a compact coverage-summary.json with per-file and total percent-covered values for statements, branches, functions, and lines. So one describes test pass/fail outcomes, the other describes how much code was exercised. Both can run in the same invocation: vitest --coverage --reporter=json --outputFile=results.json with coverage.reporter set to ['json-summary'] in the config. The names are confusingly similar but they live in unrelated subsystems.
How do I run only a subset of tests via JSON glob config?
Vitest uses glob patterns, not JSON Pointer or JSON Path, but the patterns themselves live inside the JS/TS config object. The include and exclude fields under test in vitest.config.ts each take a string array of globs — for example test.include: ['src/**/*.test.ts', 'tests/**/*.spec.ts'] and test.exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**']. To target a single suite ad-hoc from the CLI without editing the config, pass a positional file pattern: vitest run src/api or vitest run --testNamePattern="auth". For monorepos, Vitest 3 projects let you split include/exclude per project so unit, integration, and e2e suites have separate config blocks but share one runner invocation. You can also gate with --changed to run only tests affected by uncommitted changes.
How do I serialize a custom class in JSON snapshots?
Register a snapshot serializer in vitest.config.ts via the test.snapshotSerializers field — it accepts an array of paths to modules that each export a { test, serialize } pair. The test function receives a value and returns true for the types this serializer handles; serialize converts the value to the string Vitest writes into the snapshot. The same plugin format Jest uses (pretty-format plugins) works in Vitest unchanged, so existing serializers for Immutable, Enzyme, DOM nodes, and custom domain objects all carry over. For one-off cases without a config change, call expect.addSnapshotSerializer({ test, serialize }) at the top of a test file. Stable, deterministic serialization is the key — include only fields that are meaningful to the test and strip volatile fields like timestamps, autogenerated IDs, and request hashes that would cause flaky snapshot churn.
How does Vitest 3 differ from Jest in JSON output structure?
Vitest 3 deliberately mirrors the Jest JSON output shape — numTotalTests, numPassedTests, testResults[], assertionResults[] — so existing Jest-aware tooling (Codecov, Allure, GitHub Actions test reporters) works without adaptation. The deltas are small: Vitest emits ISO timestamps with millisecond precision rather than Jest's epoch numbers in some fields, and the perTestCoverage and coverageMap fields are absent in the test-result JSON because Vitest splits coverage into its own coverage-final.json. Snapshot results also differ slightly: Vitest reports unmatched, matched, updated, and added counts at the file level but does not embed snapshot content in the JSON. If you migrate a Jest-era CI pipeline to Vitest 3, the JSON reporter is the smoothest seam — point your existing parser at vitest --reporter=json --outputFile=test-results.json and most fields land where the parser expects.
Further reading and primary sources
- Vitest Docs — Configuration Reference — Authoritative list of every field under test in vitest.config.ts
- Vitest Docs — Reporters — Built-in reporters including verbose, json, junit, tap, and the reporter API
- Vitest Docs — Coverage — Coverage providers (v8, istanbul), reporters, and thresholds.autoUpdate
- Vitest Docs — Workspaces and Projects — Native projects field in Vitest 3 and migration from vitest.workspace.ts
- Istanbul Coverage Reporters — Underlying coverage report formats — json-final, json-summary, lcov, text