Vitest

TestingUnit TestingJavaScriptTypeScriptViteModern

GitHub Overview

vitest-dev/vitest

Next generation testing framework powered by Vite.

Stars14,667
Watchers54
Forks1,385
Created:December 3, 2021
Language:TypeScript
License:MIT License

Topics

testtesting-toolsvite

Star History

vitest-dev/vitest Star History
Data as of: 7/20/2025, 02:18 AM

Testing Tool

Vitest

Overview

Vitest is a next-generation testing framework powered by Vite. It leverages Vite's transform and module resolution to achieve extremely fast and lightweight test execution. With native TypeScript support and Jest-compatible API, it makes migration from existing projects seamless. The development experience resembles HMR (Hot Module Replacement), where test changes are reflected instantly, significantly improving development efficiency.

Details

Vitest is designed as a testing framework optimized for modern frontend development, with exceptional compatibility with Vite projects. It takes an ESM-first approach, enabling high-speed execution through native ES module support. It provides built-in code coverage reporting, concurrent test execution, and smart re-testing features in watch mode.

For mocking functionality, it uses vi.mock() to easily perform dynamic imports and module stubbing. Browser API mocking is available with happy-dom or jsdom, enabling efficient DOM manipulation testing. It also supports snapshot testing, async testing, and custom matcher creation out of the box.

Pros and Cons

Pros

  • Fast Execution: Up to 10x faster than Jest using Vite's transform engine
  • Zero Configuration: No setup required for TypeScript, JSX, ESM
  • Jest Compatible API: Existing Jest tests can migrate with minimal changes
  • Concurrent Execution: Default worker thread-based parallel test execution
  • Code Coverage: Built-in coverage reporting with c8 or istanbul
  • Watch Mode: File change detection and smart re-testing features

Cons

  • New Tool: Less information and plugins available compared to Jest
  • Browser Support: Real browser testing requires additional configuration
  • Learning Curve: Introduction overhead if unfamiliar with Vite
  • Debug Environment: Limited debugging support in some IDEs

Reference Pages

Code Examples

Hello World

// vitest.config.js
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'node'
  }
})

// hello.test.js
import { describe, it, expect } from 'vitest'

describe('Hello World', () => {
  it('should return greeting message', () => {
    function greet(name) {
      return `Hello, ${name}!`
    }
    
    expect(greet('Vitest')).toBe('Hello, Vitest!')
  })
})

Basic Testing

import { describe, it, expect, beforeEach, afterEach } from 'vitest'

describe('Calculator', () => {
  let calculator

  beforeEach(() => {
    calculator = {
      add: (a, b) => a + b,
      subtract: (a, b) => a - b,
      multiply: (a, b) => a * b
    }
  })

  it('should add numbers correctly', () => {
    expect(calculator.add(2, 3)).toBe(5)
    expect(calculator.add(-1, 1)).toBe(0)
  })

  it('should handle edge cases', () => {
    expect(calculator.add(0.1, 0.2)).toBeCloseTo(0.3)
    expect(calculator.multiply(5, 0)).toBe(0)
  })

  it('should validate input types', () => {
    expect(() => calculator.add('a', 2)).toThrow()
  })
})

Mocking

import { describe, it, expect, vi, beforeEach } from 'vitest'

describe('API Client', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('should mock fetch calls', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve({ data: 'test data' })
    })
    
    global.fetch = mockFetch

    const response = await fetch('/api/data')
    const data = await response.json()

    expect(mockFetch).toHaveBeenCalledWith('/api/data')
    expect(data).toEqual({ data: 'test data' })
  })

  it('should spy on module methods', () => {
    const consoleSpy = vi.spyOn(console, 'log')
    
    console.log('test message')
    
    expect(consoleSpy).toHaveBeenCalledWith('test message')
    consoleSpy.mockRestore()
  })
})

Async Testing

import { describe, it, expect } from 'vitest'

describe('Async Operations', () => {
  it('should handle promises', async () => {
    const asyncFunction = () => {
      return new Promise(resolve => {
        setTimeout(() => resolve('async result'), 100)
      })
    }

    const result = await asyncFunction()
    expect(result).toBe('async result')
  })

  it('should test promise rejections', async () => {
    const failingFunction = () => {
      return Promise.reject(new Error('Operation failed'))
    }

    await expect(failingFunction()).rejects.toThrow('Operation failed')
  })

  it('should handle async/await with timeout', async () => {
    const slowFunction = () => {
      return new Promise(resolve => {
        setTimeout(() => resolve('slow result'), 50)
      })
    }

    await expect(slowFunction()).resolves.toBe('slow result')
  }, 1000) // 1 second timeout
})

Setup and Teardown

import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'

describe('Database Operations', () => {
  beforeAll(async () => {
    // Run once before all tests in suite
    await setupDatabase()
  })

  afterAll(async () => {
    // Run once after all tests in suite
    await cleanupDatabase()
  })

  beforeEach(async () => {
    // Run before each test
    await seedTestData()
  })

  afterEach(async () => {
    // Run after each test
    await clearTestData()
  })

  it('should create user', async () => {
    const user = await createUser({ name: 'Test User' })
    expect(user.id).toBeDefined()
    expect(user.name).toBe('Test User')
  })

  it('should update user', async () => {
    const user = await createUser({ name: 'Original Name' })
    const updatedUser = await updateUser(user.id, { name: 'Updated Name' })
    expect(updatedUser.name).toBe('Updated Name')
  })
})

Advanced Features

import { describe, it, expect, vi } from 'vitest'

describe('Advanced Features', () => {
  it('should support custom matchers', () => {
    expect.extend({
      toBeWithinRange(received, floor, ceiling) {
        const pass = received >= floor && received <= ceiling
        if (pass) {
          return {
            message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`,
            pass: true,
          }
        } else {
          return {
            message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
            pass: false,
          }
        }
      },
    })

    expect(15).toBeWithinRange(10, 20)
  })

  it('should test with snapshots', () => {
    const data = {
      id: 123,
      name: 'Test User',
      createdAt: '2024-01-01T00:00:00Z'
    }

    expect(data).toMatchSnapshot()
  })

  it('should support parametrized tests', ({ value, expected }) => {
    expect(Math.abs(value)).toBe(expected)
  }, [
    { value: -5, expected: 5 },
    { value: 10, expected: 10 },
    { value: -15, expected: 15 }
  ])

  it('should mock timers', async () => {
    vi.useFakeTimers()

    const callback = vi.fn()
    setTimeout(callback, 1000)

    expect(callback).not.toHaveBeenCalled()

    vi.advanceTimersByTime(1000)
    expect(callback).toHaveBeenCalled()

    vi.useRealTimers()
  })
})