Test API
Basic Tests
import { describe, expect, it, test } from 'vitest'
test('adds numbers', () => {
expect(1 + 1).toBe(2)
})
// Alias: it
it('works the same', () => {
expect(true).toBe(true)
})Async Tests
test('async test', async () => {
const result = await fetchData()
expect(result).toBeDefined()
})Test Options
test('with options', { timeout: 10_000, retry: 2 }, async () => {})
test('with tags', { tags: ['db', 'slow'] }, async () => {})Modifiers
test.skip('skipped', () => {})
test.only('only this runs', () => {})
test.todo('implement later')
test.fails('expected to fail', () => { expect(1).toBe(2) })
test.skipIf(process.env.CI)('not in CI', () => {})
test.runIf(process.env.CI)('only in CI', () => {})
// Dynamic skip
test('dynamic', ({ skip }) => {
skip(someCondition, 'reason')
})Concurrent & Sequential
test.concurrent('parallel 1', async ({ expect }) => {})
test.concurrent('parallel 2', async ({ expect }) => {})
test.sequential('must run alone', async () => {})Important: Use { expect } from context for concurrent tests.
Parameterized Tests
test.each([
[1, 1, 2],
[1, 2, 3],
])('add(%i, %i) = %i', (a, b, expected) => {
expect(a + b).toBe(expected)
})
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
])('add($a, $b) = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected)
})
// test.for - preferred, doesn't spread arrays
test.for([
[1, 1, 2],
[1, 2, 3],
])('add(%i, %i) = %i', ([a, b, expected], { expect }) => {
expect(a + b).toBe(expected)
})Describe/Suite
describe('Math', () => {
test('adds', () => expect(1 + 1).toBe(2))
test('subtracts', () => expect(3 - 1).toBe(2))
})
// Nested
describe('User', () => {
describe('when logged in', () => {
test('shows dashboard', () => {})
})
})
// Modifiers
describe.skip('skipped', () => {})
describe.only('only this', () => {})
describe.concurrent('parallel', () => {})
describe.shuffle('random order', () => {})
describe.each([{ name: 'Chrome' }, { name: 'Firefox' }])('$name', ({ name }) => {})Lifecycle Hooks
import { afterAll, afterEach, beforeAll, beforeEach } from 'vitest'
beforeAll(async () => { await setupDatabase() })
afterAll(async () => { await teardownDatabase() })
beforeEach(async () => { await clearTestData() })
afterEach(async () => { await cleanupMocks() })
// Return cleanup function
beforeAll(async () => {
const server = await startServer()
return async () => { await server.close() }
})Around Hooks
import { aroundEach, aroundAll } from 'vitest'
aroundEach(async (runTest) => {
await db.beginTransaction()
await runTest() // Must be called!
await db.rollback()
})Test Hooks
import { onTestFailed, onTestFinished } from 'vitest'
test('with cleanup', () => {
const db = connect()
onTestFinished(() => db.close())
onTestFailed(({ task }) => { console.log('Failed:', task.result?.errors) })
})
// Reusable pattern
function useTestDb() {
const db = connect()
onTestFinished(() => db.close())
return db
}Custom Fixtures
import { test as base } from 'vitest'
const test = base.extend<{ db: Database; user: User }>({
db: async ({}, use) => {
const db = await createDb()
await use(db)
await db.close()
},
user: async ({ db }, use) => {
const user = await db.createUser({ name: 'Test' })
await use(user)
await db.deleteUser(user.id)
},
})
test('query', async ({ db, user }) => {
const found = await db.findUser(user.id)
expect(found).toEqual(user)
})
// Fixture options
const test = base.extend({
setup: [async ({}, use) => { await use() }, { auto: true }], // Always run
connection: [async ({}, use) => { /* ... */ }, { scope: 'file' }], // Once per file
})Hook Execution Order
beforeAll(in order)beforeEach(in order)- Test
afterEach(reverse order)afterAll(reverse order)
Configure with sequence.hooks: 'stack' | 'list' | 'parallel'