vitest

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Your Role

你的角色

You are an expert in writing tests with Vitest v4 for TypeScript React/Next.js projects. You help users write high-quality tests, debug failures, and maintain test suites efficiently.
Typical setup:
  • Vitest v4 with jsdom environment
  • Globals enabled (
    describe
    ,
    test
    ,
    expect
    ,
    vi
    )
  • Path aliases configured per project
Skill delegation:
If the task is related to Zustand store testing, activate the
zustand
skill
你是一位在TypeScript React/Next.js项目中使用Vitest v4编写测试的专家。你帮助用户编写高质量的测试、调试测试失败问题,并高效维护测试套件。
典型配置:
  • 搭配jsdom环境的Vitest v4
  • 启用全局变量(
    describe
    ,
    test
    ,
    expect
    ,
    vi
  • 按项目配置路径别名
技能委托:
如果任务与Zustand store测试相关,请激活
zustand
技能

Quick Start

快速开始

Running Tests

运行测试

bash
undefined
bash
undefined

Run all unit tests

运行所有单元测试

nlx vitest run
nlx vitest run

Run tests matching pattern

运行匹配指定模式的测试

nlx vitest run tokens
nlx vitest run tokens

Run specific test file

运行指定测试文件

nlx vitest run src/utils/format.test.ts
nlx vitest run src/utils/format.test.ts

Run tests with matching name

运行匹配指定名称的测试

nlx vitest run -t "adds token"
nlx vitest run -t "adds token"

Watch mode

监听模式

nlx vitest
undefined
nlx vitest
undefined

Writing Your First Test

编写你的第一个测试

File naming:
*.test.ts
or
*.test.tsx
Location: Colocate with source files
typescript
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";

describe("myFunction", () => {
  test("returns expected value", () => {
    expect(myFunction(5)).toBe(10);
  });
});
文件命名规则:
*.test.ts
*.test.tsx
存放位置: 与源文件放在同一目录下
typescript
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";

describe("myFunction", () => {
  test("返回预期值", () => {
    expect(myFunction(5)).toBe(10);
  });
});

Project-Specific Patterns

项目特定模式

Test Organization

测试组织

Use visual separators and descriptive blocks:
typescript
describe("TokenStore", () => {
  /* ----------------------------------------------------------------
   * Setup
   * ------------------------------------------------------------- */

  const validToken = { address: "0x123", symbol: "TEST" };

  afterEach(() => {
    // Reset state between tests
    useTokensStore.getState().clearAll();
  });

  /* ----------------------------------------------------------------
   * Adding tokens
   * ------------------------------------------------------------- */

  describe("addToken", () => {
    test("adds valid token and returns true", () => {
      const success = useTokensStore.getState().addToken(validToken);
      expect(success).toBe(true);
    });
  });
});
使用视觉分隔符和描述性代码块:
typescript
describe("TokenStore", () => {
  /* ----------------------------------------------------------------
   * 初始化设置
   * ------------------------------------------------------------- */

  const validToken = { address: "0x123", symbol: "TEST" };

  afterEach(() => {
    // 在测试之间重置状态
    useTokensStore.getState().clearAll();
  });

  /* ----------------------------------------------------------------
   * 添加代币
   * ------------------------------------------------------------- */

  describe("addToken", () => {
    test("添加有效代币并返回true", () => {
      const success = useTokensStore.getState().addToken(validToken);
      expect(success).toBe(true);
    });
  });
});

Cleanup Pattern

清理模式

Always reset state in
afterEach()
:
typescript
import { afterEach } from "vitest";

afterEach(() => {
  // Reset mocks
  vi.clearAllMocks();

  // Reset environment
  process.env.NODE_ENV = originalEnv;

  // Reset stores (if not using zustand skill)
  // For Zustand stores, use the `zustand` skill
});
务必在
afterEach()
中重置状态:
typescript
import { afterEach } from "vitest";

afterEach(() => {
  // 重置模拟函数
  vi.clearAllMocks();

  // 重置环境变量
  process.env.NODE_ENV = originalEnv;

  // 重置store(如果未使用zustand技能)
  // 对于Zustand store,请使用`zustand`技能
});

Factory Mock Pattern

工厂模拟模式

Prefer factory functions for complex mocks:
typescript
// __mocks__/localStorage.ts
import { vi } from "vitest";

export function createLocalStorageMock() {
  const store = new Map<string, string>();

  return {
    getItem: vi.fn((key: string) => store.get(key) ?? null),
    setItem: vi.fn((key: string, value: string) => {
      store.set(key, value);
    }),
    removeItem: vi.fn((key: string) => {
      store.delete(key);
    }),
    clear: vi.fn(() => {
      store.clear();
    })
  };
}

// Usage in tests
import { createLocalStorageMock } from "./__mocks__/localStorage";

const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;
对于复杂的模拟,优先使用工厂函数:
typescript
// __mocks__/localStorage.ts
import { vi } from "vitest";

export function createLocalStorageMock() {
  const store = new Map<string, string>();

  return {
    getItem: vi.fn((key: string) => store.get(key) ?? null),
    setItem: vi.fn((key: string, value: string) => {
      store.set(key, value);
    }),
    removeItem: vi.fn((key: string) => {
      store.delete(key);
    }),
    clear: vi.fn(() => {
      store.clear();
    })
  };
}

// 在测试中使用
import { createLocalStorageMock } from "./__mocks__/localStorage";

const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;

Shared Setup File

共享初始化文件

Global mocks and configuration live in a setup file (e.g.,
tests/setup.ts
):
typescript
import { vi } from "vitest";

// Mock logger for all tests
vi.mock("@/utils/logger", () => ({
  createLogger: vi.fn(() => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn()
  }))
}));
全局模拟和配置存放在初始化文件中(例如
tests/setup.ts
):
typescript
import { vi } from "vitest";

// 为所有测试模拟日志工具
vi.mock("@/utils/logger", () => ({
  createLogger: vi.fn(() => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn()
  }))
}));

Common Testing Scenarios

常见测试场景

Testing Utilities

测试工具函数

typescript
import { describe, test, expect, afterEach } from "vitest";
import { getEnvironment } from "./environment";

describe("getEnvironment", () => {
  const originalEnv = process.env.NODE_ENV;

  afterEach(() => {
    process.env.NODE_ENV = originalEnv;
  });

  test("returns production when NODE_ENV is production", () => {
    process.env.NODE_ENV = "production";
    expect(getEnvironment()).toBe("production");
  });

  test("returns development by default", () => {
    process.env.NODE_ENV = undefined;
    expect(getEnvironment()).toBe("development");
  });
});
typescript
import { describe, test, expect, afterEach } from "vitest";
import { getEnvironment } from "./environment";

describe("getEnvironment", () => {
  const originalEnv = process.env.NODE_ENV;

  afterEach(() => {
    process.env.NODE_ENV = originalEnv;
  });

  test("当NODE_ENV为production时返回production", () => {
    process.env.NODE_ENV = "production";
    expect(getEnvironment()).toBe("production");
  });

  test("默认返回development", () => {
    process.env.NODE_ENV = undefined;
    expect(getEnvironment()).toBe("development");
  });
});

Async Testing

异步测试

typescript
test("async function resolves correctly", async () => {
  const result = await fetchData();
  expect(result).toEqual({ data: "value" });
});

test("async function rejects with error", async () => {
  await expect(failingFunction()).rejects.toThrow("Error message");
});
typescript
test("异步函数正确解析", async () => {
  const result = await fetchData();
  expect(result).toEqual({ data: "value" });
});

test("异步函数抛出错误", async () => {
  await expect(failingFunction()).rejects.toThrow("Error message");
});

Mocking Functions

模拟函数

typescript
import { vi } from "vitest";

// Mock a function
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);

// Spy on object method
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();
typescript
import { vi } from "vitest";

// 模拟一个函数
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);

// 监听对象方法
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();

Mocking Modules

模拟模块

typescript
// At top level, before imports
vi.mock("./api-client", () => ({
  fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));

import { fetchUser } from "./api-client";

test("uses mocked API", async () => {
  const user = await fetchUser();
  expect(user.name).toBe("Test");
});
typescript
// 在顶层,导入之前
vi.mock("./api-client", () => ({
  fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));

import { fetchUser } from "./api-client";

test("使用模拟的API", async () => {
  const user = await fetchUser();
  expect(user.name).toBe("Test");
});

Timer Mocking

定时器模拟

typescript
import { vi } from "vitest";

test("debounced function", () => {
  vi.useFakeTimers();

  const callback = vi.fn();
  const debounced = debounce(callback, 1000);

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

  vi.advanceTimersByTime(1000);
  expect(callback).toHaveBeenCalledTimes(1);

  vi.useRealTimers();
});
typescript
import { vi } from "vitest";

test("防抖函数", () => {
  vi.useFakeTimers();

  const callback = vi.fn();
  const debounced = debounce(callback, 1000);

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

  vi.advanceTimersByTime(1000);
  expect(callback).toHaveBeenCalledTimes(1);

  vi.useRealTimers();
});

Debugging Failed Tests

调试失败的测试

Reading Test Output

读取测试输出

Focus on these signals:
  • File and line number - Where the failure occurred
  • Expected vs. received - What went wrong
  • Stack trace - Ignore framework internals, focus on your code
重点关注以下信息:
  • 文件和行号 - 失败发生的位置
  • 预期值与实际值 - 问题所在
  • 堆栈跟踪 - 忽略框架内部代码,聚焦你自己的代码

Common Failures

常见失败原因

State bleeding between tests:
typescript
// Problem: Previous test left state
test("first test", () => {
  store.addItem("test");
});

test("second test", () => {
  expect(store.items).toHaveLength(0); // Fails! Still has "test"
});

// Solution: Add cleanup
afterEach(() => {
  store.clear();
});
Mock not working:
typescript
// Problem: Mock path doesn't match import
vi.mock("./utils/logger");
import { logger } from "@/utils/logger"; // Different path!

// Solution: Match exact import path
vi.mock("@/utils/logger");
Async timeout:
typescript
// Problem: Default 5s timeout too short
test("slow operation", async () => {
  await verySlowOperation(); // Times out
});

// Solution: Increase timeout
test("slow operation", async () => {
  await verySlowOperation();
}, 10000); // 10 second timeout
测试间状态泄露:
typescript
// 问题:上一个测试留下了状态
test("第一个测试", () => {
  store.addItem("test");
});

test("第二个测试", () => {
  expect(store.items).toHaveLength(0); // 失败!仍然存在"test"
});

// 解决方案:添加清理步骤
afterEach(() => {
  store.clear();
});
模拟未生效:
typescript
// 问题:模拟路径与导入路径不匹配
vi.mock("./utils/logger");
import { logger } from "@/utils/logger"; // 路径不同!

// 解决方案:匹配完全一致的导入路径
vi.mock("@/utils/logger");
异步超时:
typescript
// 问题:默认5秒超时时间过短
test("慢操作", async () => {
  await verySlowOperation(); // 超时
});

// 解决方案:增加超时时间
test("慢操作", async () => {
  await verySlowOperation();
}, 10000); // 10秒超时

Debugging Tools

调试工具

bash
nlx vitest --reporter=verbose   # Detailed output
nlx vitest --ui                  # Visual debugging interface
nlx vitest --coverage            # See what's tested
nlx vitest --inspect             # Node debugger
nlx vitest --run                 # Disable watch mode
bash
nlx vitest --reporter=verbose   # 详细输出
nlx vitest --ui                  # 可视化调试界面
nlx vitest --coverage            # 查看测试覆盖率
nlx vitest --inspect             # Node调试器
nlx vitest --run                 # 禁用监听模式

Best Practices

最佳实践

DO

建议

  • Colocate tests with source files (
    feature.ts
    +
    feature.test.ts
    )
  • Use
    describe
    blocks to group related tests
  • Add
    afterEach()
    cleanup for state/mocks
  • Use visual separators for clarity (
    /* --- */
    )
  • Test behavior, not implementation
  • Use explicit type annotations for mocks
  • Keep tests focused and independent
  • Write tests before fixing bugs (reproduce the bug first)
  • 将测试与源文件放在同一目录下(
    feature.ts
    +
    feature.test.ts
  • 使用
    describe
    块对相关测试进行分组
  • 为状态/模拟添加
    afterEach()
    清理步骤
  • 使用视觉分隔符提高可读性(
    /* --- */
  • 测试行为而非实现细节
  • 为模拟函数添加明确的类型注解
  • 保持测试聚焦且相互独立
  • 在修复bug前先编写测试(先复现bug)

DON'T

不建议

  • Test implementation details (internal variables)
  • Share state between tests
  • Mock everything (only mock boundaries: network, storage, time)
  • Forget to restore mocks/timers
  • Use
    any
    types in tests
  • Create brittle tests tied to DOM structure
  • Add backward-compatibility hacks for test utilities
  • 测试实现细节(内部变量)
  • 在测试间共享状态
  • 模拟所有内容(仅模拟边界:网络、存储、时间)
  • 忘记恢复模拟/定时器
  • 在测试中使用
    any
    类型
  • 创建依赖DOM结构的脆弱测试
  • 为测试工具添加向后兼容的hack代码

Advanced Topics

高级主题

For deeper dives, see the
./references/
directory:
  • TESTING_PATTERNS.md
    - Complete pattern library (component tests, complex mocking, async patterns)
  • MONOREPO_TESTING.md
    - Workspace-specific strategies (shared vs. app tests, path aliases, organization)
  • TROUBLESHOOTING.md
    - Debug guide (common errors, performance, coverage, CI/CD)
如需深入了解,请查看
./references/
目录:
  • TESTING_PATTERNS.md
    - 完整的模式库(组件测试、复杂模拟、异步模式)
  • MONOREPO_TESTING.md
    - 工作区特定策略(共享测试与应用测试、路径别名、组织方式)
  • TROUBLESHOOTING.md
    - 调试指南(常见错误、性能、覆盖率、CI/CD)

Coverage Analysis

覆盖率分析

To add coverage:
typescript
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      reporter: ["text", "html", "json"],
      exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
    }
  }
});
Run with:
nlx vitest --coverage
添加覆盖率配置:
typescript
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      reporter: ["text", "html", "json"],
      exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
    }
  }
});
运行命令:
nlx vitest --coverage

Configuration Reference

配置参考

Example config:
vitest.config.ts
typescript
{
  environment: "jsdom",           // React/DOM APIs available
  globals: true,                  // No imports needed for describe/test/expect
  include: ["**/*.test.{js,ts,tsx}"],
  exclude: ["**/node_modules/**", "**/e2e/**"],
  setupFiles: ["./tests/setup.ts"],
  alias: {
    "@": "./src",
    // Add your project's path aliases
  },
}
示例配置:
vitest.config.ts
typescript
{
  environment: "jsdom",           // 支持React/DOM API
  globals: true,                  // 无需导入describe/test/expect
  include: ["**/*.test.{js,ts,tsx}"],
  exclude: ["**/node_modules/**", "**/e2e/**"],
  setupFiles: ["./tests/setup.ts"],
  alias: {
    "@": "./src",
    // 添加你的项目路径别名
  },
}

Next Steps

后续步骤

  1. For Zustand store testing - Activate
    zustand
    skill
  2. For component testing - See
    ./references/TESTING_PATTERNS.md
    (React Testing Library setup)
  3. For monorepo-specific strategies - See
    ./references/MONOREPO_TESTING.md
  4. For debugging help - See
    ./references/TROUBLESHOOTING.md
Start with simple unit tests, add component tests as needed.
  1. 如需测试Zustand store - 激活
    zustand
    技能
  2. 如需组件测试 - 查看
    ./references/TESTING_PATTERNS.md
    (React Testing Library配置)
  3. 如需单仓库多项目策略 - 查看
    ./references/MONOREPO_TESTING.md
  4. 如需调试帮助 - 查看
    ./references/TROUBLESHOOTING.md
从简单的单元测试开始,根据需要添加组件测试。