View all services
Talk to QA Advisor
Browse the Knowledge Hub211 resources
Tools Library · Review and guide

Vitest review: what it is, pros, cons and setup

Vitest is a test framework that runs on Vite’s module pipeline, so TypeScript, JSX and ES modules work through the same configuration and plugins as the application. It offers a largely Jest-compatible API with built-in mocking, snapshots and coverage, plus a Browser Mode that runs tests in real browsers. Vitest 5 was released on 3 September 2026 and is free under the MIT licence.

QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.

last reviewed
13 October 2026
version covered
Vitest 5
licence
Open source

Reviewed by Viral Patel

Co-Founder, QAbleLast reviewed 13 October 2026

At a glance

Vitest at a glance

Official site
Vendor
Vitest project
Category
Unit testing
Licence
Open source (MIT)
Platforms
Node.js, Browser
Languages
JavaScript, TypeScript
Integrates with
Vite, Testing Library, Playwright
First released
2021
Version reviewed
Vitest 5

Verdict

Our verdict on Vitest

Strengths

  • Reuses vite.config, so aliases, plugins and TypeScript handling match the application with no separate transform setup
  • TypeScript and native ES modules run without Babel or ts-jest
  • Jest-style API: describe, test, expect, snapshots and vi in place of jest
  • Browser Mode runs the same tests in real Chromium, Firefox or WebKit through Playwright
  • Sharding with blob reports and merge-reports is built in, with no paid service
  • Free under MIT, with VoidZero committing to keep it MIT-licensed

Weaknesses

  • Vitest 5 changed defaults and removed APIs, and many guides still describe version 3 or 4
  • Jest compatibility is close but not complete; mock resets and module mocks behave differently
  • Globals are off by default, which breaks Jest-style tests and Testing Library auto-cleanup
  • Requires Node 22.12 or later and Vite 6.4 or later as a peer dependency

Our take

Vitest is the natural choice for any project already built with Vite, and a strong one for TypeScript or ESM-first codebases generally, because there is no separate transform setup to keep in step with the build. QAble has evaluated it rather than maintained a long-running suite on it, so treat this as a bounded take. Version 5 is only a little over a month old and tightens several behaviours at once: clearMocks is on by default, vi.mock calls must be at the top level, unawaited async assertions fail the test and benchmarks moved to a new API. For an existing suite, run the migration guide on a branch before upgrading. When migrating from Jest, the API similarity hides real differences in mock reset behaviour, module mocking and globals, so budget for test fixes rather than a find-and-replace.

We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.

How it works

What is Vitest and how does it work?

Vite’s pipeline does the transforming

Vitest starts a Vite server in its main process and loads every test file and its imports through Vite’s module runner, which applies the same plugins, aliases and transforms as the application build. TypeScript, JSX, CSS imports and framework single-file components therefore work without a second configuration, and changes to vite.config apply to tests as well. A separate vitest.config file can extend or override it. Because transformed modules are cached by the server, watch mode re-runs only the tests affected by a change, using Vite’s module graph to work out which ones.

Pools of workers, isolated by file

Test files run in a pool of workers. The default pool is forks, child processes, chosen for stability; threads, vmThreads and vmForks are alternatives that trade isolation or compatibility for speed. Each file is isolated by default, so module state does not leak between files, and tests within a file run one after another unless marked concurrent. On large machines the single Vite server in the main process can become the bottleneck, which is why the documentation suggests sharding across processes even on one machine.

Three ways to provide a DOM

The environment option chooses node, jsdom or happy-dom for each test file or project, which suits logic and simple component tests. Browser Mode goes further: tests run inside a real browser driven by a provider package, @vitest/browser-playwright or the community-maintained @vitest/browser-webdriverio, with locators and interactions sent through the provider. That gives real layout, events and rendering, and since 4.0 the mode is stable. Version 5 adds a trace view that records each interaction as a DOM snapshot to step through after a failure.

Mocking through module interception

vi.mock replaces a module for the test file. Vitest hoists vi.mock calls above the imports, so the mock is in place before the code under test loads it, and in version 5 those calls must be at the top level of the file or Vitest throws. vi.fn and vi.spyOn create mock functions, vi.mocked adds types, and vi.when, new in version 5, sets return values per argument list. Unlike Jest, there is no automatic loading of __mocks__ folders without a vi.mock call, and importing the real module inside a factory is asynchronous, through vi.importActual.

Strengths

What it does well

One configuration for app and tests

Aliases, environment variables, plugins and TypeScript paths come from the Vite configuration, so a test imports modules exactly as the application does. The class of failure where tests pass with different resolution rules from the build largely disappears.

Projects for mixed environments

The projects option runs node, jsdom and browser tests from one command with one report. In version 5, inline projects inherit the root configuration by default, nested projects are supported for monorepos, and -p selects a project by name.

Browser Mode with visual checks

Browser Mode runs component and integration tests in real browsers with an interaction API and locators, which are strict by default in version 5. toMatchScreenshot adds visual regression comparisons, and the version 5 trace view replays failed tests step by step without a separate viewer.

Type tests alongside runtime tests

expectTypeOf and assertType in *.test-d.ts files check types through tsc or vue-tsc as part of the test run. Library authors can assert that a public API infers the types they promise, and a regression fails the build like any other test.

Sharding and merged reports

--shard splits test files across machines, the blob reporter stores each shard’s results in .vitest/blob, and --merge-reports combines them into one report. Since 4.1 the GitHub Actions reporter also writes a job summary.

Tools for finding slow and flaky tests

The duration breakdown shows how much time goes to environment setup, imports, transforms and tests. Version 5 prints performance hints by default, adds vitest doctor to try alternative configurations, and adds --repeats to run each test several times when hunting flakiness.

Pricing

Vitest pricing and licensing

Vitest is free and open source under the MIT licence, copyright VoidZero Inc. and contributors. VoidZero, which employs core maintainers, joined Cloudflare on 4 June 2026 and stated that Vite, Vitest and its other projects will remain open source and MIT-licensed. There is no paid edition and no feature held back. Vite+, VoidZero’s bundled toolchain that runs Vitest through vp test, reached 1.0 on 28 September 2026 and is also free under MIT. Costs are CI time and upgrade effort.

  • Licence: MIT for Vitest and its official packages, stated in the vitest-dev/vitest repository.
  • No tiers: Browser Mode, coverage, sharding, the UI and the HTML reporter are all in the open-source packages on npm.
  • Funding: core maintainers work on Vite and Vitest at VoidZero, with further support from sponsors through GitHub Sponsors and Open Collective.
  • Infrastructure: CI minutes scale with suite size; Browser Mode adds the cost of installing and running real browsers on runners.
  • Hidden cost: majors. Version 4 removed several configuration options and version 5 changed defaults, so each major needs a planned migration.

Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.

Getting started

Vitest tutorial: getting started

Setup, step by step

  1. Use Node.js 22.12 or later.
  2. Install Vitest as a dev dependency; Yarn users add vite explicitly, since it is now a peer dependency.
  3. Add .vitest/ to .gitignore; reporters and artifacts write there in version 5.
  4. Create a file whose name contains .test. or .spec., such as cart.test.ts.
  5. Import describe, test and expect from vitest, since globals are off by default.
  6. Add "test": "vitest" to package.json scripts, and run npm test for watch mode or npx vitest run once.

Install

npm install -D vitest
# watch mode while developing
npx vitest
# single run, as CI does
npx vitest run

A first example

// src/cart.test.ts
import { describe, expect, test } from 'vitest'
import { Cart } from './cart'

describe('Cart', () => {
  test('sums line items in pence', () => {
    const cart = new Cart()
    cart.add('SKU-1', { quantity: 2, unitPence: 1500 })
    cart.add('SKU-2', { quantity: 1, unitPence: 499 })
    expect(cart.totalPence()).toBe(3499)
  })

  test('rejects a zero quantity', () => {
    const cart = new Cart()
    expect(() => cart.add('SKU-1', { quantity: 0, unitPence: 1500 }))
      .toThrow('quantity must be positive')
  })

  test('fails to load a missing cart', async () => {
    // Vitest 5 fails the test if this assertion is not awaited
    await expect(Cart.load('missing-id')).rejects.toThrow('not found')
  })
})

What trips people up first

ReferenceError: describe is not defined, or expect is not defined, usually in tests copied from Jest. Vitest does not inject globals unless asked. Either import describe, test, expect and vi from vitest in each file, which is the documented default, or set globals: true in the test configuration and add vitest/globals to the TypeScript types. The same setting affects Testing Library: without globals, its automatic cleanup after each test does not run, so components from one test remain in the DOM for the next. Import cleanup and call it in afterEach, or enable globals.

In practice

Using Vitest in practice

Configuring projects and coverage

Keep test settings in vitest.config.ts, using defineConfig from vitest/config, or in the test block of vite.config. Split suites by environment with projects: fast unit tests in node, component tests in jsdom or the browser. Coverage is configured at the root and, since version 4, reports only files that tests load unless you set coverage.include, so set it to your source folder or untested files vanish from the report. The V8 provider is the default choice and since version 4 remaps coverage through the AST, which the release post describes as matching Istanbul’s accuracy.

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'], // otherwise untested files are not reported
    },
    projects: [
      {
        // Inline projects inherit the root config by default in Vitest 5
        test: { name: 'unit', environment: 'node', include: ['src/**/*.unit.test.ts'] },
      },
      {
        test: {
          name: 'browser',
          include: ['src/**/*.browser.test.tsx'],
          browser: {
            enabled: true,
            provider: playwright(),
            instances: [{ browser: 'chromium' }],
          },
        },
      },
    ],
  },
})

Mocking modules the Vitest way

Put vi.mock at the top level of the file; Vitest hoists it above imports, and version 5 throws if it is nested inside a test or a function. A factory must return the module’s exports explicitly, including default if the module has one. To keep part of the real module, await vi.importActual inside the factory. Use vi.mocked for typed access to mocked functions, and vi.when for per-argument behaviour instead of branching implementations. clearMocks now defaults to true, so call history resets before every test while implementations stay in place.

// src/orders.test.ts
import { expect, test, vi } from 'vitest'
import { placeOrder } from './orders'
import { charge } from './payment-client'

// Hoisted above the imports; must stay at the top level in Vitest 5
vi.mock('./payment-client', async () => {
  const actual = await vi.importActual<typeof import('./payment-client')>('./payment-client')
  return { ...actual, charge: vi.fn() }
})

test('charges the card once and marks the order paid', async () => {
  vi.mocked(charge).mockResolvedValue({ id: 'ch_1', status: 'succeeded' })

  const order = await placeOrder({ totalPence: 4200, card: 'tok_visa' })

  expect(charge).toHaveBeenCalledTimes(1)
  expect(charge).toHaveBeenCalledWith(4200, 'tok_visa')
  expect(order.status).toBe('paid')
})

Sharding in GitHub Actions

The documented pattern is a matrix of shard jobs, each running with the blob reporter, followed by one job that downloads every blob and merges them. Blobs land in .vitest/blob in version 5, and the upload step must include hidden files because the folder name starts with a dot. Shards split test files, not individual tests, so keep file sizes roughly even. The GitHub Actions reporter is added automatically when Vitest detects it is running in Actions, so failures are annotated on the pull request without extra configuration.

# .github/workflows/test.yml
name: test
on: [push, pull_request]
jobs:
  vitest:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx vitest run --reporter=blob --shard=${{ matrix.shard }}/${{ strategy.job-total }}
      - uses: actions/upload-artifact@v7
        if: ${{ !cancelled() }}
        with:
          name: blob-${{ matrix.shard }}
          path: .vitest
          include-hidden-files: true
  merge:
    if: ${{ !cancelled() }}
    needs: vitest
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - uses: actions/download-artifact@v8
        with:
          path: .vitest
          merge-multiple: true
      - run: npx vitest run --merge-reports

Migrating a Jest suite

Start by replacing jest with vi and importing from vitest, or set globals: true to keep the global style. Then work through the differences the official migration guide lists. mockReset restores the original implementation in Vitest, where Jest leaves an empty function. Module mock factories must return explicit exports, and __mocks__ folders load only when vi.mock is called. jest.requireActual becomes an awaited vi.importActual. The done callback is not supported, so convert callback tests to async functions. Test names are joined with a greater-than sign, which changes -t patterns, and JEST_WORKER_ID becomes VITEST_POOL_ID. Snapshot headers differ, so regenerate snapshots once and review the diff.

Upgrading to Vitest 5

Upgrade Node to 22.12 or later and Vite to 6.4 or later first. Then expect four kinds of failure. Tests that relied on call history carrying between tests break because clearMocks defaults to true. vi.mock calls inside functions now throw. Unawaited resolves, rejects and toMatchFileSnapshot assertions fail instead of warning, and expect.poll rejects on timeout. Benchmarks need rewriting, because bench is now a fixture inside test rather than a top-level import. Also check custom matcher typings, which take the return type as their first parameter, and any tooling that read reports from the old locations now that everything is written under .vitest. Teams on version 3 should read the version 4 migration guide first.

Pitfalls

Common pitfalls

Copying Jest tests that rely on globals

Why it happens: Vitest does not inject describe, test, expect or vi unless globals is enabled, so copied tests fail with reference errors and Testing Library stops cleaning up between tests.

The fix: Import the functions from vitest, or set globals: true and add vitest/globals to tsconfig types. Without globals, call Testing Library’s cleanup in afterEach.

vi.mock inside a test or helper

Why it happens: Vitest hoists vi.mock to the top of the file. Placed inside a function it never applied as written, and since version 5 Vitest throws instead of guessing.

The fix: Keep vi.mock at the top level, use vi.hoisted for values the factory needs, and use vi.doMock for the rare case where a mock must be applied later.

Assuming mockReset matches Jest

Why it happens: In Vitest, mockReset restores the original implementation passed to vi.fn; in Jest it leaves a function that returns undefined. Migrated tests then call real code unexpectedly.

The fix: Read the Migrating from Jest guide before converting, set explicit implementations in beforeEach, and rely on the clearMocks default for call history.

Coverage that ignores untested files

Why it happens: Version 4 removed coverage.all, so by default only files that some test imports appear in the report. A module with no tests at all is simply absent, and the percentage looks better than it is.

The fix: Set coverage.include to the source folders that should be measured, so untested files appear with zero coverage.

Unawaited async assertions

Why it happens: Older versions awaited a forgotten expect(...).resolves at the end of the test and printed a warning. Version 5 fails the test, so suites that upgraded without fixing the warnings go red.

The fix: Await every resolves, rejects and toMatchFileSnapshot assertion. Lint rules for floating promises catch these before they reach CI.

Following configuration examples from version 3 or earlier

Why it happens: workspace files, poolMatchGlobs, environmentMatchGlobs, poolOptions, maxThreads, the basic reporter and deps.inline were removed or renamed in version 4, and many articles still show them.

The fix: Use projects instead of workspace and match globs, maxWorkers instead of maxThreads and maxForks, server.deps.inline instead of deps.inline, and check the migration guide for each removed option.

Compared

How Vitest compares

ToolIncludedCode transformationNative ES modulesReal browser runsLicence
VitestRunner, assertions, mocks, snapshots, coverageVite pipeline and pluginsYesBrowser Mode via Playwright or WebdriverIOMIT
JestRunner, assertions, mocks, snapshots, coverageBabel by default, or ts-jestExperimental, behind a Node flagNo; jsdom simulationMIT
MochaRunner only; add Chai, SinonNone built inYesRuns in browsers as well as NodeMIT

Head-to-head comparisons

Vitest vs Jest

FAQ

Frequently asked questions

What is Vitest?

Vitest is a test framework that runs on Vite’s module pipeline, so TypeScript, JSX and ES modules work through the same configuration and plugins as the application. It offers a largely Jest-compatible API with built-in mocking, snapshots and coverage, plus a Browser Mode that runs tests in real browsers. Vitest 5 was released on 3 September 2026 and is free under the MIT licence.

Is Vitest free?

Yes. Vitest is open source under the MIT licence, with no paid edition or feature tiers; Browser Mode, coverage, sharding and the UI are all free. It is developed by VoidZero and community contributors. VoidZero joined Cloudflare in June 2026 and stated that Vitest will remain open source and MIT-licensed.

Vitest vs Jest: which should I choose?

Choose Vitest for projects built with Vite and for TypeScript or ESM-first codebases, since it shares the application’s configuration and needs no separate transform. Choose Jest for CommonJS or Babel-based projects, React Native, or a large existing suite where migration would cost more than it saves. The APIs are similar enough that the build tooling, not the test syntax, usually decides.

Do I need a Vite app to use Vitest?

No. Vitest works in any Node.js project, including back-end services and libraries, and simply uses Vite internally to transform files. Since version 5, vite is a required peer dependency, which npm and pnpm install automatically; Yarn users add it explicitly. A vitest.config.ts file is enough configuration for a project that has no vite.config.

What changed in Vitest 5?

Vitest 5, released on 3 September 2026, focuses on performance and stricter defaults. It requires Node 22.12 and Vite 6.4 or later, turns clearMocks on by default, requires vi.mock at the top level, fails unawaited async assertions, rewrites the benchmarking API and writes reports under .vitest. New features include a Browser Mode trace view, nested projects that inherit the root config, vi.when and mocking of Temporal in fake timers.

Can Vitest run tests in a real browser?

Yes. Browser Mode, stable since version 4, runs tests inside real browsers through a provider: @vitest/browser-playwright for Chromium, Firefox and WebKit, or the community-maintained WebdriverIO provider. A preview provider exists for local viewing only. Browser tests get real layout and events, locators and visual comparisons with toMatchScreenshot, which jsdom-based tests cannot offer.

How hard is it to migrate from Jest to Vitest?

Usually moderate. Most matchers and the describe and test structure carry over, and replacing jest with vi handles much of the code. The work is in the differences: globals, mockReset behaviour, module mock factories, asynchronous vi.importActual, unsupported done callbacks and snapshot headers. Migrate one folder at a time with both runners installed, and follow the official Migrating from Jest guide.

Alternatives

Vitest alternatives worth weighing

Jest

JavaScript testing framework with bundled mocking, coverage and snapshot testing, zero configuration to start.

Mocha

Flexible JavaScript test framework that supplies structure and lets you choose assertions and mocking.

Resources

Where to go next

Ready

Picking the wrong tool costs more than picking late.

QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.

Talk to QA Advisor