← All skills

Vitest Skill

Unit testingJavaScriptTypeScript

Copy and Paste in your Terminal

npx skills add https://github.com/LambdaTest/agent-skills.git --skill vitest-skill

Advanced patterns

Advanced topics and patterns for experienced users.

Vitest — Advanced Patterns & Playbook

In-Source Testing

// src/utils.ts — tests live alongside code
export function sum(a: number, b: number) { return a + b; }

if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;
  it('adds numbers', () => expect(sum(1, 2)).toBe(3));
}

Advanced Mocking

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

// Auto-mock entire module
vi.mock('./api', () => ({
  fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
  updateUser: vi.fn()
}));

// Partial mock — keep real implementations
vi.mock('./utils', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./utils')>();
  return { ...actual, formatDate: vi.fn(() => '2025-01-01') };
});

// Mock globals
vi.stubGlobal('fetch', vi.fn());
vi.stubGlobal('IntersectionObserver', vi.fn(() => ({
  observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn()
})));

// Timer control
describe('Debounce', () => {
  beforeEach(() => { vi.useFakeTimers(); });
  afterEach(() => { vi.useRealTimers(); });

  it('debounces calls', () => {
    const fn = vi.fn();
    const debounced = debounce(fn, 300);
    debounced(); debounced(); debounced();
    vi.advanceTimersByTime(300);
    expect(fn).toHaveBeenCalledOnce();
  });
});

// Spy on class methods
const spy = vi.spyOn(UserService.prototype, 'save');
spy.mockResolvedValue({ id: 1 });

Concurrent & Parallel Testing

// Run tests concurrently within a suite
describe.concurrent('independent API tests', () => {
  it('fetches users', async () => { /* ... */ });
  it('fetches products', async () => { /* ... */ });
  it('fetches orders', async () => { /* ... */ });
});

// Pool configuration in vitest.config.ts
export default defineConfig({
  test: {
    pool: 'threads',        // or 'forks', 'vmThreads'
    poolOptions: { threads: { maxThreads: 8, minThreads: 2 } },
    isolate: true,           // true = full isolation per test file
    fileParallelism: true
  }
});

Snapshot Testing

// Inline snapshot
it('creates user shape', () => {
  expect(createUser('Alice')).toMatchInlineSnapshot(`
    { "id": StringMatching /^[a-f0-9-]+$/, "name": "Alice" }
  `);
});

// File snapshot (saved to __snapshots__)
it('renders component', () => {
  const html = render(<Button variant="primary">Click</Button>);
  expect(html).toMatchSnapshot();
});

// Custom serializer
expect.addSnapshotSerializer({
  serialize: (val) => `MyType(${val.name})`,
  test: (val) => val?.__type === 'MyType'
});

Vue/React Component Testing

import { mount } from '@vue/test-utils';
// or: import { render, screen } from '@testing-library/react';

describe('Counter Component', () => {
  it('increments on click', async () => {
    const wrapper = mount(Counter, { props: { initial: 0 } });
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toContain('1');
  });

  it('emits update event', async () => {
    const wrapper = mount(Counter);
    await wrapper.find('button').trigger('click');
    expect(wrapper.emitted('update')).toHaveLength(1);
    expect(wrapper.emitted('update')[0]).toEqual([1]);
  });
});

Coverage & Configuration

// vitest.config.ts — production-grade
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./test/setup.ts'],
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html', 'lcov'],
      exclude: ['node_modules/', 'test/', '**/*.d.ts', '**/*.config.*'],
      thresholds: { branches: 80, functions: 80, lines: 80, statements: 80 }
    },
    alias: { '@': path.resolve(__dirname, './src') },
    reporters: ['default', 'junit'],
    outputFile: { junit: './reports/junit.xml' },
    typecheck: { enabled: true }
  }
});

Workspace Configuration (Monorepo)

// vitest.workspace.ts
export default ['packages/*', 'apps/*'];

// Each package gets its own vitest.config.ts
// Run: vitest --workspace

Anti-Patterns

  • ❌ Using jest.fn() instead of vi.fn() — Vitest has its own API
  • import.meta.env in tests without vi.stubEnv — env vars leak between tests
  • vi.mock() inside it() — must be at file scope (hoisted automatically)
  • ❌ Missing vi.useRealTimers() cleanup — fake timers leak to next test
  • ❌ Not using pool: 'forks' for CPU-bound tests — threads share memory