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 }) })