Unit Testing with Vitest

· 4 min read · Updated March 11, 2026 · beginner
testing vitest unit-tests jest

Vitest is a lightning-fast test runner for JavaScript and TypeScript, designed to work seamlessly with Vite. If you have used Jest, you will feel right at home — but with better performance and modern defaults.

In this tutorial, you will learn how to set up Vitest, write your first tests, and explore features like snapshot testing, mocking, and test coverage.

Why Vitest?

Vitest offers several advantages over traditional test runners:

  • Near-instant hot module replacement (HMR) — tests run in the same process as your dev server
  • Native ES modules — no need for Babel or complex transformations
  • Jest-compatible API — most Jest tests work with zero changes
  • TypeScript support out of the box — no additional configuration needed

Setting Up Vitest

First, install Vitest in your project:

npm install -D vitest

Add a test script to your package.json:

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

Create a vitest.config.js in your project root:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
    include: ['**/*.test.js', '**/*.spec.js'],
  },
});

Now create your first test file:

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

function sum(a, b) {
  return a + b;
}

describe('sum()', () => {
  it('adds two numbers correctly', () => {
    expect(sum(2, 3)).toBe(5);
  });

  it('handles negative numbers', () => {
    expect(sum(-1, -1)).toBe(-2);
  });
});

Run your tests:

npm test

Understanding Vitest Globals

Vitest provides global APIs by default — no imports needed:

// These are available globally
describe('Math operations', () => {
  it('multiplies numbers', () => {
    expect(3 * 4).toBe(12);
  });
  
  it('checks equality', () => {
    expect({ a: 1 }).toEqual({ a: 1 });
  });
  
  it('checks truthiness', () => {
    expect('hello').toBeTruthy();
    expect(null).toBeFalsy();
  });
});

If you prefer explicit imports, disable globals in your config:

export default defineConfig({
  test: {
    globals: false,
  },
});

Test Matchers

Vitest includes all Jest-compatible matchers plus some extras:

import { it, expect } from 'vitest';

it('demonstrates common matchers', () => {
  // Equality
  expect(10).toBe(10);
  expect({ a: 1 }).toEqual({ a: 1 });
  
  // Truthiness
  expect('test').toBeTruthy();
  expect('').toBeFalsy();
  expect(null).toBeNull();
  expect(undefined).toBeUndefined();
  
  // Numbers
  expect(10).toBeGreaterThan(5);
  expect(10).toBeLessThan(20);
  expect(10).toBeGreaterThanOrEqual(10);
  
  // Strings
  expect('hello world').toContain('hello');
  expect('hello').toMatch(/^hel/);
  
  // Arrays
  expect([1, 2, 3]).toContain(2);
  expect([1, 2, 3]).toHaveLength(3);
});

Async Testing

Testing async code is straightforward with Vitest:

import { it, expect } from 'vitest';

function fetchUser(id) {
  return Promise.resolve({ id, name: 'Alice' });
}

it('handles async functions', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Alice');
});

it('handles promises with resolves/rejects', () => {
  expect(Promise.resolve('success')).resolves.toBe('success');
  expect(Promise.reject(new Error('fail'))).rejects.toThrow('fail');
});

Mocking

Vitest includes a powerful mocking API:

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

// Mock a function
const fetchData = vi.fn(() => Promise.resolve({ data: 'mocked' }));

it('mocks a function', async () => {
  const result = await fetchData();
  expect(fetchData).toHaveBeenCalled();
  expect(result.data).toBe('mocked');
});

// Mock modules
vi.mock('./api', () => ({
  getUser: () => Promise.resolve({ name: 'Mocked User' }),
}));

// Mock timers
it('tests setTimeout', async () => {
  vi.useFakeTimers();
  
  const callback = vi.fn();
  setTimeout(callback, 1000);
  
  vi.advanceTimersByTime(1000);
  
  expect(callback).toHaveBeenCalled();
  
  vi.useRealTimers();
});

Snapshot Testing

Snapshots capture rendered output and compare against saved versions:

import { it, expect } from 'vitest';

function formatUser(user) {
  return 'Name: ' + user.name + ', Age: ' + user.age;
}

it('matches snapshot', () => {
  const output = formatUser({ name: 'Alice', age: 30 });
  expect(output).toMatchSnapshot();
});

// Update snapshots with: vitest update

Running Tests

Vitest provides several ways to run tests:

# Watch mode (default in dev)
npm test

# Run once
npm run test:run

# Run specific file
npx vitest run src/utils.test.js

# Run with coverage
npx vitest run --coverage

# Run tests matching a pattern
npx vitest run -t "adds two numbers"

Configuration Options

Here is a comprehensive vitest.config.js example:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // Test environment
    environment: 'node', // or 'jsdom', 'happy-dom'
    
    // Glob patterns for test files
    include: ['**/*.test.js', '**/*.spec.js'],
    
    // Exclude patterns
    exclude: ['**/node_modules/**', '**/dist/**'],
    
    // Global setup
    setupFiles: ['./vitest.setup.js'],
    
    // Coverage configuration
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
    },
    
    // Test timeout (ms)
    testTimeout: 5000,
    
    // Hook timeout
    hookTimeout: 10000,
  },
});

Summary

You have learned how to:

  1. Set up Vitest in your project
  2. Write tests using the Jest-compatible API
  3. Use matchers for assertions
  4. Test async code with promises
  5. Mock functions and modules
  6. Create snapshot tests
  7. Configure Vitest for different scenarios

Vitest speed and developer experience make it an excellent choice for modern JavaScript projects. The zero-config setup and Vite integration mean you can start testing in seconds.

In the next tutorial, we will explore testing asynchronous code in more depth.