Returning a Promise instead of using done callback
Tests can return a Promise instead of using a done callback. Instead of accepting a done parameter and calling done() at the end, wrap the test code in a new Promise and resolve it. This pattern replaces the callback-based approach with Promise-based test completion. Example: replace `it('should work', (done) => { ... done() })` with `it('should work', () => new Promise(done => { ... done() }))`.
Promise-based test completion example
Example showing how to convert a done callback test to a Promise-based test: `it('should work', () => new Promise(done => { // ... done() }))`.
Use .only to focus on specific tests or suites
Add `.only` to a test or describe block to run only that test or suite and skip everything else in the file. For example, `describe.only('suite', () => {...})` or `it.only('focused test', () => {...})`. When any test or suite in a file is marked with `.only`, all unmarked tests in that file are skipped. By default, Vitest will fail the entire test run if it encounters `.only` in CI (when `process.env.CI` is set), controlled by the `allowOnly` configuration option.
Use .skip to temporarily disable tests
Add `.skip` to a test or describe block to temporarily disable it without deleting the code: `describe.skip('skipped suite', () => {...})` or `it.skip('skipped test', () => {...})`. Skipped tests still show up in the report so you don't forget about them. This is useful when a test is flaky or depends on an external service that's temporarily down.
Use .todo as a placeholder for planned tests
Use `.todo` to mark tests as planned but not yet written: `describe.todo('unimplemented suite')` or `it.todo('unimplemented test')`. Unlike `.skip`, a `.todo` test has no test body and is purely a placeholder for future work. It shows up in the report as a reminder.
Concurrent tests example
import { describe, it } from 'vitest'
// The two tests marked with concurrent will be started in parallel
describe('suite', () => {
it('serial test', async () => { /* ... */ })
it.concurrent('concurrent test 1', async ({ expect }) => { /* ... */ })
it.concurrent('concurrent test 2', async ({ expect }) => { /* ... */ })
})
Mocking with vi object
Vitest provides jest-compatible APIs on the vi object. Create mock functions with vi.fn(), check if function is mocked with vi.isMockFunction(), access calls with fn.mock.calls, set implementation with fn.mockImplementation(), and access results with fn.mock.results.
Mocking example with vi
import { expect, vi } from 'vitest'
const fn = vi.fn()
fn('hello', 1)
expect(vi.isMockFunction(fn)).toBe(true)
expect(fn.mock.calls[0]).toEqual(['hello', 1])
fn.mockImplementation((arg: string) => arg)
fn('world', 2)
expect(fn.mock.results[1].value).toBe('world')
Running tests concurrently with .concurrent
Use .concurrent on individual tests to start them in parallel within a suite. When .concurrent is used on a describe suite itself, every test in that suite will be started in parallel.
Chai and Jest expect compatibility
Chai is built-in for assertions with Jest expect-compatible APIs. Setting test.globals to true in config provides better compatibility with third-party libraries that add matchers.
Common web idioms support
Vitest provides out-of-the-box support for ES Module, TypeScript, JSX, and PostCSS.
Concurrent tests and test context
When running concurrent tests, Snapshots and Assertions must use expect from the local Test Context to ensure the right test is detected.
Concurrent suite example
import { describe, it } from 'vitest'
// All tests within this suite will be started in parallel
describe.concurrent('suite', () => {
it('concurrent test 1', async ({ expect }) => { /* ... */ })
it('concurrent test 2', async ({ expect }) => { /* ... */ })
it.concurrent('concurrent test 3', async ({ expect }) => { /* ... */ })
})
Test environment creation costs by configuration
DOM environment creation costs vary by configuration: jsdom costs roughly 200-500ms per import and happy-dom roughly 90-200ms, plus time to construct the window. With an isolating pool (the default), this cost is paid for every test file. Three configurations reduce this cost with different trade-offs: pool 'forks'/'threads' + isolate true (default) - once per file with safest but slowest behavior; pool 'vmThreads' - once per worker with fresh VM context and window per file but cross-realm instanceof edge cases; isolate false - once per worker with no isolation but tests must not depend on clean window or module state.
Prefer isolate false with threads for best performance
Prefer isolate: false with threads pool if the tests tolerate shared state: it is the fastest option and keeps memory behavior simple. Use vmThreads when every file needs a fresh window and the per-file environment cost dominates the run. happy-dom is cheaper to create than jsdom in every setup.
Test isolation default behavior by pool type
By default Vitest runs every test file in an isolated environment based on the pool: threads pool runs every test file in a separate Worker, forks pool runs every test file in a separate forked child process, vmThreads pool runs every test file in a separate VM context but uses workers for parallelism.
Cannot disable isolation with vmThreads pool
If using vmThreads pool, you cannot disable isolation. Use threads pool instead to improve test performance.