DEV Community

Otto
Otto

Posted on

Vitest in 2026: The Testing Framework That Makes You Actually Want to Write Tests

Vitest in 2026: The Testing Framework That Makes You Actually Want to Write Tests

Let's be honest: most developers don't enjoy writing tests. They're slow to set up, painful to maintain, and Jest's configuration is... let's call it 'interesting'. Vitest changes the game entirely.

What is Vitest?

Vitest is a blazing-fast unit testing framework powered by Vite. It uses the same configuration, transforms, and resolvers as your Vite app โ€” meaning zero additional setup for projects already using Vite (React, Vue, Svelte, SvelteKit, Nuxt, Astro...).

Why developers love it:

  • โšก 10-20x faster than Jest on large codebases
  • ๐Ÿ”ง Zero config (if you use Vite)
  • ๐ŸŽญ Jest-compatible API (migrate in minutes)
  • ๐Ÿ‘€ Native TypeScript support (no ts-jest nonsense)
  • ๐Ÿ”ฅ HMR for tests (tests re-run only when affected files change)

Getting Started

npm install -D vitest
Enter fullscreen mode Exit fullscreen mode

If you're using Vite, add to your vite.config.ts:

import { defineConfig } from 'vite'
import { defineConfig as defineTestConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,           // Use describe/it/expect without imports
    environment: 'node',     // 'jsdom' for browser-like tests
    coverage: {
      provider: 'v8',        // or 'istanbul'
      reporter: ['text', 'html', 'json'],
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

For standalone projects:

npm install -D vitest
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage",
    "test:ui": "vitest --ui"
  }
}
Enter fullscreen mode Exit fullscreen mode

Your First Tests

// src/utils/math.ts
export const add = (a: number, b: number) => a + b
export const divide = (a: number, b: number) => {
  if (b === 0) throw new Error('Division by zero')
  return a / b
}
Enter fullscreen mode Exit fullscreen mode
// src/utils/math.test.ts
import { describe, it, expect } from 'vitest'
import { add, divide } from './math'

describe('Math utilities', () => {
  describe('add', () => {
    it('should add two numbers', () => {
      expect(add(2, 3)).toBe(5)
    })

    it('should handle negative numbers', () => {
      expect(add(-1, 1)).toBe(0)
    })
  })

  describe('divide', () => {
    it('should divide correctly', () => {
      expect(divide(10, 2)).toBe(5)
    })

    it('should throw on division by zero', () => {
      expect(() => divide(10, 0)).toThrow('Division by zero')
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

Mocking โ€” Vitest's Superpower

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

// Mock an entire module
vi.mock('./database', () => ({
  db: {
    findUser: vi.fn(),
    saveUser: vi.fn(),
  }
}))

// Mock a specific function
const fetchMock = vi.fn().mockResolvedValue({
  ok: true,
  json: () => Promise.resolve({ id: '1', name: 'Alice' })
})
vi.stubGlobal('fetch', fetchMock)

// Spy on existing functions
import { db } from './database'

describe('UserService', () => {
  beforeEach(() => {
    vi.clearAllMocks() // Reset mocks between tests
  })

  it('should fetch and save a user', async () => {
    vi.mocked(db.findUser).mockResolvedValue(null)
    vi.mocked(db.saveUser).mockResolvedValue({ id: '1', name: 'Alice' })

    const result = await createUser({ name: 'Alice', email: 'alice@example.com' })

    expect(db.saveUser).toHaveBeenCalledOnce()
    expect(db.saveUser).toHaveBeenCalledWith({
      name: 'Alice',
      email: 'alice@example.com'
    })
    expect(result.name).toBe('Alice')
  })
})
Enter fullscreen mode Exit fullscreen mode

Testing React Components with Vitest

npm install -D @testing-library/react @testing-library/jest-dom jsdom
Enter fullscreen mode Exit fullscreen mode
// vite.config.ts โ€” add jsdom environment
export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test-setup.ts'],
  },
})
Enter fullscreen mode Exit fullscreen mode
// src/test-setup.ts
import '@testing-library/jest-dom'
Enter fullscreen mode Exit fullscreen mode
// src/components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Counter } from './Counter'

describe('Counter', () => {
  it('renders with initial count', () => {
    render(<Counter initialCount={5} />)
    expect(screen.getByText('Count: 5')).toBeInTheDocument()
  })

  it('increments on button click', () => {
    render(<Counter initialCount={0} />)
    fireEvent.click(screen.getByRole('button', { name: '+' }))
    expect(screen.getByText('Count: 1')).toBeInTheDocument()
  })
})
Enter fullscreen mode Exit fullscreen mode

Vitest UI โ€” The Game Changer

npm install -D @vitest/ui
vitest --ui
Enter fullscreen mode Exit fullscreen mode

This opens a browser-based UI showing:

  • All your test files and suites
  • Pass/fail status with details
  • Coverage reports
  • Real-time updates as you code

It's genuinely beautiful and makes debugging tests 10x easier.

Snapshot Testing

import { it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { Button } from './Button'

it('renders correctly', () => {
  const { container } = render(<Button variant="primary">Click me</Button>)
  expect(container).toMatchSnapshot()
})

// Inline snapshots (great for small outputs)
it('formats currency', () => {
  expect(formatCurrency(1234.56, 'EUR')).toMatchInlineSnapshot('"โ‚ฌ1,234.56"')
})
Enter fullscreen mode Exit fullscreen mode

Benchmarking (Bonus Feature)

import { bench, describe } from 'vitest'

describe('Array operations', () => {
  bench('filter + map', () => {
    [1,2,3,4,5].filter(n => n > 2).map(n => n * 2)
  })

  bench('reduce', () => {
    [1,2,3,4,5].reduce((acc, n) => n > 2 ? [...acc, n * 2] : acc, [] as number[])
  })
})
Enter fullscreen mode Exit fullscreen mode

Migrating from Jest

Good news: Vitest is mostly Jest-compatible. The migration is usually:

  1. npm uninstall jest @types/jest babel-jest ts-jest
  2. npm install -D vitest
  3. Change imports: from 'jest' โ†’ from 'vitest'
  4. Replace jest.fn() โ†’ vi.fn(), jest.mock() โ†’ vi.mock()
  5. Update package.json scripts

For most projects, that's it. Total time: 30-60 minutes.

Performance Comparison

Metric Jest Vitest
Cold start ~2-4s ~200ms
500 tests ~45s ~8s
TypeScript via ts-jest native
ESM support painful native
HMR โŒ โœ…

Conclusion

Vitest has become the default testing framework for modern JavaScript/TypeScript projects in 2026. If you're starting a new Vite project, it's a no-brainer. If you're on Jest and feeling the pain, the migration is easier than you think.

The bottom line: better DX = more tests written = better software.


Building a freelance dev business? My Freelancer OS Notion Template helps you manage clients, projects, and finances in one place โ€” โ‚ฌ19.

Happy testing! ๐Ÿงช

Top comments (0)