Vitest https://vitest.dev/ npm i -D vitest
{
"scripts": {
"test": "vitest"
}
}
Example
import { expect, test } from 'vitest'
import { sum } from './sum.js'
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
Parallel tests
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 }) => { /* ... */ })
})
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 }) => { /* ... */ })
})
In-source testing My add test into your implementation source code Allows to share the same closure as the implementations and able to test against private states without exporting
// the implementation
export function add(...args: number[]): number {
return args.reduce((a, b) => a + b, 0)
}
// in-source test suites
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
it('add', () => {
expect(add()).toBe(0)
expect(add(1)).toBe(1)
expect(add(1, 2, 3)).toBe(6)
})
}
Type Testing
import { assertType, expectTypeOf, test } from 'vitest'
import { mount } from './mount.js'
test('my types work properly', () => {
expectTypeOf(mount).toBeFunction()
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>()
// @ts-expect-error name is a string
assertType(mount({ name: 42 }))
})
test (it) api
import { expect, test } from 'vitest'
test('should work as expected', () => {
expect(Math.sqrt(4)).toBe(2)
})
it('should work as expected', () => {
expect(Math.sqrt(4)).toBe(2)
})
skip
import { assert, test } from 'vitest'
test.skip('skipped test', () => {
// Test skipped, no error
assert.equal(Math.sqrt(4), 3)
})
skipIf
import { assert, test } from 'vitest'
const isDev = process.env.NODE_ENV === 'development'
test.skipIf(isDev)('prod only test', () => {
// this test only runs in production
})
runIf Opposite to skipIf
import { assert, test } from 'vitest'
const isDev = process.env.NODE_ENV === 'development'
test.runIf(isDev)('dev only test', () => {
// this test only runs in development
})
only Only run certain tests in a given suite
import { assert, test } from 'vitest'
test.only('test', () => {
// Only this test (and others marked with only) are run
assert.equal(Math.sqrt(4), 2)
})
concurrent Run tests in parallel
import { describe, test } from 'vitest'
// The two tests marked with concurrent will be run in parallel
describe('suite', () => {
test('serial test', async () => { /* ... */ })
test.concurrent('concurrent test 1', async () => { /* ... */ })
test.concurrent('concurrent test 2', async () => { /* ... */ })
})
sequential Marks a test as sequential. This is useful if you want to run tests in sequence within describe.concurrent
import { describe, test } from 'vitest'
// with config option { sequence: { concurrent: true } }
test('concurrent test 1', async () => { /* ... */ })
test('concurrent test 2', async () => { /* ... */ })
test.sequential('sequential test 1', async () => { /* ... */ })
test.sequential('sequential test 2', async () => { /* ... */ })
// within concurrent suite
describe.concurrent('suite', () => {
test('concurrent test 1', async () => { /* ... */ })
test('concurrent test 2', async () => { /* ... */ })
test.sequential('sequential test 1', async () => { /* ... */ })
test.sequential('sequential test 2', async () => { /* ... */ })
})
todo Tests to be implemented later
test.todo('unimplemented test')
fails Indicate that an assertion will fail explicitly
import { expect, test } from 'vitest'
function myAsyncFunc() {
return new Promise(resolve => resolve(1))
}
test.fails('fail test', async () => {
await expect(myAsyncFunc()).rejects.toBe(1)
})
describe api describe lets you organize your tests and benchmarks so reports are more clear describe blocks can be nested to create a hierarchy of tests
// basic.spec.ts
// organizing tests
import { describe, expect, test } from 'vitest'
const person = {
isActive: true,
age: 32,
}
describe('person', () => {
test('person is defined', () => {
expect(person).toBeDefined()
})
test('is active', () => {
expect(person.isActive).toBeTruthy()
})
test('age limit', () => {
expect(person.age).toBeLessThanOrEqual(32)
})
})
apis describe - create a test suite describe.skip avoid running a particular describe block. describe.skipIf skip the suite whenever the condition is truthy describe.runIf opposite of describe.skipIf describe.only only run certain suites describe.concurrent runs all inner suites and tests in parallel describe.sequential tests in sequence within describe.concurrent describe.shuffle run all tests in random order describe.todo stub suites to be implemented later Tests lifecycle helps to avoid repeating setup and teardown code beforeEach, afterEach, beforeAll, afterAll beforeEach callback to be called before each of the tests in the current context accepts an optional cleanup function, equivalent to afterEach afterEach c allback to be called after each one of the tests in the current context beforeAll callback to be called once before starting to run all tests in the current context accepts an optional cleanup function equivalent to afterAll afterAll called once after all tests have run in the current context
import { beforeEach } from 'vitest'
beforeEach(async () => {
// Clear mocks and add some testing data after before each test run
await stopMocking()
await addUser({ name: 'John' })
})
// clean up function, called once after each test run
return async () => {
await resetSomething()
}
import { afterEach } from 'vitest'
afterEach(async () => {
await clearTestingData() // clear testing data after each test run
})
import { beforeAll } from 'vitest'
beforeAll(async () => {
// called once before all tests run
await startMocking()
// clean up function, called once after all tests run
return async () => {
await stopMocking()
}
})
import { afterAll } from 'vitest'
afterAll(async () => {
await stopMocking() // this method is called after all tests run
})
onTestFinished Called after the test has finished running It is called after afterEach hooks If you are running tests concurrently, you should use it useful when creating reusable logic
import { onTestFinished, test } from 'vitest'
test('performs a query', () => {
const db = connectDb()
onTestFinished(() => db.close())
db.query('SELECT * FROM users')
})
import { test } from 'vitest'
test.concurrent('performs a query', ({ onTestFinished }) => {
const db = connectDb()
onTestFinished(() => db.close())
db.query('SELECT * FROM users')
})
// this can be in a separate file
function getTestDb() {
const db = connectMockedDb()
onTestFinished(() => db.close())
return db
}
test('performs a user query', async () => {
const db = getTestDb()
expect(
await db.query('SELECT * from users').perform()
).toEqual([])
})
test('performs an organization query', async () => {
const db = getTestDb()
expect(
await db.query('SELECT * from organizations').perform()
).toEqual([])
})
onTestFailed called only after the test has failed It is called after afterEach If you are running tests concurrently, you should use it
import { onTestFailed, test } from 'vitest'
test('performs a query', () => {
const db = connectDb()
onTestFailed((e) => {
console.log(e.result.errors)
})
db.query('SELECT * FROM users')
})
Mocking fake function vi.fn()
import { expect, vi, it } from 'vitest'
it('should return ...', () => {
const fn = vi.fn()
fn('hello', 1)
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledWith('hello', 1)
// same as fn.mockReturnValue(arg)
// same as vi.fn(() => arg)
fn.mockImplementation((arg) => arg)
const res = fn('world', 2)
expect(res).toBe('world')
})
Mock an exported function Main logic may use other functions which we do not want to run or wish to make them return some specific values or we wish to check if they were called or not etc... We need to mock it Function should be located non in the same file as the main function We need to import the whole module with import * as utils from './utils' Then spy on specific function vi.spyOn(utils, 'myFunction').mockImplementation(() => 'mocked!')
// 📁 utils.ts
export function myFunction() {
return 'original result'
}
// 📁 main.ts
import { myFunction } from './utils'
export function run() {
return myFunction()
}
// 📁 main.test.ts
import { describe, it, expect, vi } from 'vitest'
import * as utilsModule from './utils'
import { run } from './main'
vi.spyOn(utilsModule, 'myFunction').mockImplementation(() => 'mocked!')
describe('run', () => {
it('uses mocked myFunction', () => {
expect(run()).toBe('mocked!')
})
})
Mock complete module You may mock all functions inside the file Functions will become vi.fn()
// 📁 main.test.ts
// ✅ Auto-mock the entire module
vi.mock('./utils')
import * as utilsModule from './utils'
import { run } from './main'
describe('run', () => {
it('uses mocked myFunction - version 1', () => {
utilsModule.myFunction.mockImplementation(() => 'mocked 1')
expect(run()).toBe('mocked 1')
expect(utilsModule.myFunction).toHaveBeenCalled()
})
it('uses mocked myFunction - version 2', () => {
utilsModule.myFunction.mockImplementation(() => 'mocked 2')
expect(run()).toBe('mocked 2')
expect(utilsModule.myFunction).toHaveBeenCalled()
})
})
Mock a package Same way we may mock a package in full
import { describe, it, expect, vi } from 'vitest'
import axios from 'axios'
vi.mock('axios') // all methods become mocked with vi.fn()
it('mocks only axios.get', async () => {
axios.get.mockResolvedValue({ data: 'hello' })
const response = await axios.get('/test')
expect(response.data).toBe('hello')
expect(axios.get).toHaveBeenCalledWith('/test')
})
Or partially only get() method
import { describe, it, expect, vi } from 'vitest'
import axios from 'axios'
vi.mock('axios') // all methods become mocked with vi.fn()
it('mocks only axios.get', async () => {
vi.spyOn(axios, 'get').mockResolvedValue({ data: 'hello' })
const response = await axios.get('/test')
expect(response.data).toBe('hello')
expect(axios.get).toHaveBeenCalledWith('/test')
})
or partially (same thing, but somehow different, to be checked)
import { describe, it, expect, vi } from 'vitest'
import axios from 'axios'
// Dynamically mock axios
vi.mock(import('axios'), async (importOriginal) => {
const axios = await importOriginal()
return {
...axios, // Spread the original axios module
get: vi.fn(() => Promise.resolve({ data: 'mocked' })) // Mock only the get method
}
})
describe('axios partial mock', () => {
it('mocks only axios.get', async () => {
const response = await axios.get('/test')
expect(response.data).toBe('mocked')
expect(axios.get).toHaveBeenCalledWith('/test')
// other axios methods (like post, delete, etc.) remain real
})
})