mcpField
V
vitest

vitest

github.com/sablier-labs测试与质量940 次安装5 周前更新
代码仓库

关于 vitest

vitest 是面向 TypeScript React/Next.js 项目的 Vitest v4 测试技能,服务于编写测试、排查失败和维护测试套件的开发者。内容涵盖测试组织、模拟、异步测试、覆盖率与调试实践。

主要功能

  1. 01编写单元测试与异步测试
  2. 02模拟函数、模块和计时器
  3. 03组织测试及清理状态
  4. 04排查失败并查看覆盖率
  5. 05提供 React 与 monorepo 测试参考

适用场景

  1. 01为 TypeScript 项目添加单元测试
  2. 02测试 React/Next.js 代码与 DOM 行为
  3. 03排查模拟失效或测试状态泄漏
  4. 04评估覆盖率并调试失败用例

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sablier-labs/agent-skills --skill vitest

安装后在 Claude Code 中输入/vitest确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
monorepo-testing.md
testing-patterns.md
troubleshooting.md

详细介绍

你的角色

你是擅长为 TypeScript React/Next.js 项目使用 Vitest v4 编写测试的专家。你可以帮助用户编写 高质量测试、调试失败原因,并高效维护测试套件。

典型配置:

  • 使用 jsdom 环境的 Vitest v4
  • 启用全局 API(describe、test、expect、vi)
  • 根据项目配置路径别名

快速开始

运行测试

# Run all unit tests
nlx vitest run

# Run tests matching pattern
nlx vitest run tokens

# Run specific test file
nlx vitest run src/utils/format.test.ts

# Run tests with matching name
nlx vitest run -t "adds token"

# Watch mode
nlx vitest

编写你的第一个测试

文件命名: *.test.ts 或 *.test.tsx

存放位置: 与源文件放在一起

import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";

describe("myFunction", () => {
  test("returns expected value", () => {
    expect(myFunction(5)).toBe(10);
  });
});

项目特定模式

测试组织

使用视觉分隔符和描述性代码块:

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);
    });
  });
});

清理模式

始终在 afterEach() 中重置状态:

import { afterEach } from "vitest";

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

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

  // Reset stores
});

工厂模拟模式

复杂模拟优先使用工厂函数:

// __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;

共享设置文件

全局模拟和配置放在设置文件中(例如 tests/setup.ts):

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()
  }))
}));

常见测试场景

测试工具函数

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");
  });
});

异步测试

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");
});

模拟函数

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();

模拟模块

// 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");
});

模拟计时器

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();
});

调试失败的测试

阅读测试输出

重点关注以下信号:

  • 文件和行号 - 失败发生的位置
  • 预期值与实际值 - 出了什么问题
  • 堆栈跟踪 - 忽略框架内部信息,重点查看自己的代码

常见失败

测试之间状态相互影响:

// 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();
});

模拟不起作用:

// 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");

异步超时:

// 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

调试工具

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

最佳实践

应该做

  • 将测试与源文件放在一起(feature.ts + feature.test.ts)
  • 使用 describe 代码块分组相关测试
  • 使用 afterEach() 清理状态和模拟
  • 使用视觉分隔符提升可读性(/* --- */)
  • 测试行为,而不是实现方式
  • 为模拟添加显式类型注解
  • 让测试保持聚焦且相互独立
  • 修复 bug 前先编写测试(先复现 bug)

不应该做

  • 测试实现细节(内部变量)
  • 在测试之间共享状态
  • 什么都模拟(只模拟边界:网络、存储、时间)
  • 忘记恢复模拟和计时器
  • 在测试中使用 any 类型
  • 编写依赖 DOM 结构的脆弱测试
  • 为测试工具添加向后兼容补丁

进阶主题

如需深入了解,请参阅 ./references/ 目录:

  • testing-patterns.md - 完整模式库(组件测试、复杂模拟、异步模式)
  • monorepo-testing.md - 工作区专属策略(共享测试与应用测试、路径别名、组织方式)
  • troubleshooting.md - 调试指南(常见错误、性能、覆盖率、CI/CD)

覆盖率分析

要添加覆盖率:

// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      reporter: ["text", "html", "json"],
      exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
    }
  }
});

运行:nlx vitest --coverage

配置参考

配置示例:vitest.config.ts

{
  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
  },
}

后续步骤

  1. 组件测试 - 参阅 ./references/testing-patterns.md(React Testing Library 设置)
  2. Monorepo 专属策略 - 参阅 ./references/monorepo-testing.md
  3. 调试帮助 - 参阅 ./references/troubleshooting.md

从简单的单元测试开始,按需添加组件测试。

评论

支持 Markdown