Skip to content
intermediate Phase 7 · TypeScript Testing & Tools

Vitest Modern Testing

Use Vitest for fast TypeScript testing with native ESM support.

45m
0 problems
Topic Progress 0%

Vitest Setup

Vitest Setup

Installation

npm install --save-dev vitest

vitest.config.ts

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov', 'html'],
      include: ['src/**/*.ts'],
      exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts']
    },
    typecheck: {
      enabled: true
    }
  }
});

package.json Scripts

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage",
    "test:typecheck": "vitest typecheck"
  }
}

Basic Test

import { describe, it, expect } from 'vitest';
import { add } from './math';

describe('add', () => {
  it('adds two numbers', () => {
    expect(add(1, 2)).toBe(3);
  });
});

Vitest Features

Vitest Features

Snapshot Testing

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

interface UserProfile {
  name: string;
  email: string;
  settings: Record<string, unknown>;
}

function formatUser(user: UserProfile): string {
  return `${user.name} <${user.email}>`;
}

describe('formatUser', () => {
  it('formats user correctly', () => {
    const user: UserProfile = {
      name: 'Alice',
      email: 'alice@test.com',
      settings: {}
    };
    expect(formatUser(user)).matchSnapshot();
  });

  it('matches inline snapshot', () => {
    expect(formatUser({
      name: 'Bob',
      email: 'bob@test.com',
      settings: {}
    })).toMatchInlineSnapshot(`"Bob <bob@test.com>"`);
  });
});

Mocking

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

// Mock a module
vi.mock('./database.js', () => ({
  db: {
    query: vi.fn().mockResolvedValue([{ id: 1, name: 'Alice' }])
  }
}));

import { db } from './database.js';

// Mock a function
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>();

// Spy on console
const consoleSpy = vi.spyOn(console, 'log');

// Restore all mocks
afterEach(() => {
  vi.restoreAllMocks();
});

Fake Timers

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

describe('debounce', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('should debounce function calls', () => {
    const fn = vi.fn();
    const debounced = debounce(fn, 300);

    debounced();
    debounced();
    debounced();

    expect(fn).not.toHaveBeenCalled();

    vi.advanceTimersByTime(300);

    expect(fn).toHaveBeenCalledTimes(1);
  });
});

Type-Safe Testing

Type-Safe Testing

Type Checking in Tests

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

function processInput(input: string | number): string {
  return typeof input === 'string' ? input.toUpperCase() : input.toString();
}

describe('processInput', () => {
  it('returns string for string input', () => {
    const result = processInput('hello');
    expectTypeOf(result).toBeString();
  });

  it('returns string for number input', () => {
    const result = processInput(42);
    expectTypeOf(result).toBeString();
  });
});

// Generic function testing
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

describe('first', () => {
  it('returns correct type', () => {
    const result = first([1, 2, 3]);
    expectTypeOf(result).toEqualTypeOf<number | undefined>();
  });
});

Testing Async Code

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

async function fetchUser(id: string): Promise<{ id: string; name: string }> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

describe('fetchUser', () => {
  it('returns user data', async () => {
    const mockData = { id: '1', name: 'Alice' };
    global.fetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve(mockData)
    });

    const user = await fetchUser('1');
    expect(user).toEqual(mockData);
  });
});

Best Practices

  • Use expectTypeOf for compile-time type assertions
  • Vitest is faster than Jest for Vite projects
  • Use vi.mock() for module mocking
  • Use vi.fn() for typed function mocks
  • Enable typecheck.enabled for type safety

Vitest vs Jest

Vitest vs Jest

Performance Comparison

Feature Vitest Jest
Speed Faster (Vite-based) Slower
ESM Support Native Partial
TypeScript Built-in Requires ts-jest
Watch Mode Instant HMR File-based
Compatibility Vitest API Jest API
Config vitest.config.ts jest.config.ts

Migration from Jest

// Jest API works in Vitest - minimal changes needed
// Before (Jest)
import { describe, it, expect, jest } from '@jest/globals';

// After (Vitest) - just change import source
import { describe, it, expect, vi } from 'vitest';

// jest.fn() becomes vi.fn()
const mockFn = vi.fn();

// jest.mock() becomes vi.mock()
vi.mock('./module.js');

When to Choose Vitest

// Vitest is ideal when:
// 1. Using Vite as your bundler
// 2. You need native ESM support
// 3. You want faster test execution
// 4. You need typecheck in tests

// Jest is ideal when:
// 1. You have an existing Jest setup
// 2. You need extensive community plugins
// 3. You use Create React App (built-in Jest)
// 4. You need older Node.js support

Shared Test Utilities

// utils/test-utils.ts - works with both Vitest and Jest
export function createMockUser(overrides?: Partial<User>): User {
  return {
    id: '1',
    name: 'Test User',
    email: 'test@example.com',
    ...overrides
  };
}

export function expectToBeDefined<T>(value: T | null | undefined): asserts value is T {
  expect(value).toBeDefined();
  expect(value).not.toBeNull();
}