mcpField
J
jest

jest

github.com/bobmatnyc测试与质量78222 次安装15 周前更新
代码仓库

关于 jest

Jest 是一款通过 ts-jest 支持 TypeScript 的 JavaScript 测试框架。它集成测试运行器、断言、模拟、快照和覆盖率报告,可用于应用及 React 组件测试。

主要功能

  1. 01测试运行器与断言
  2. 02模拟函数和模块
  3. 03快照测试
  4. 04覆盖率报告
  5. 05通过 ts-jest 支持 TypeScript

适用场景

  1. 01测试 TypeScript 应用行为
  2. 02模拟 API 依赖
  3. 03配合 Testing Library 检查 React 组件
  4. 04生成测试覆盖率报告

安装

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

$npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill jest

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

附带文件metadata.json

详细介绍

Jest + TypeScript - 行业标准测试

概述

Jest 是行业标准测试框架,市场占有率达 70%,为 TypeScript 项目提供成熟且经过实战检验的生态系统。它提供全面的测试能力,内置快照测试、模拟和覆盖率报告功能。

主要特性:

  • 🏆 行业标准:市场占有率达 70%,广泛采用
  • 📦 一体化:在一个包中提供测试运行器、断言、模拟和覆盖率功能
  • 📸 快照测试:内置快照支持,可用于 UI 测试
  • 🧪 React 集成:兼容 React Testing Library、enzyme
  • 🔧 成熟的生态系统:丰富的插件、工具和社区支持
  • 🎯 TypeScript 支持:通过 ts-jest 提供完整的类型安全
  • 🔍 覆盖率报告:内置 Istanbul 覆盖率功能
  • 🌐 多平台支持:Node.js、浏览器(jsdom)、React Native

安装:

npm install -D jest @types/jest ts-jest
npm install -D @testing-library/react @testing-library/jest-dom  # For React

基本设置

1. 初始化 Jest 配置
npx ts-jest config:init

这会创建 jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
2. 手动配置

jest.config.ts(TypeScript 配置):

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.test.{ts,tsx}',
    '!src/**/__tests__/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
};

export default config;
3. TypeScript 配置

tsconfig.json:

{
  "compilerOptions": {
    "types": ["jest", "@testing-library/jest-dom"],
    "esModuleInterop": true
  }
}

tsconfig.test.json(测试专用):

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "types": ["jest", "node", "@testing-library/jest-dom"]
  },
  "include": ["src/**/*.test.ts", "src/**/*.spec.ts", "src/**/__tests__/**"]
}
4. Package.json 脚本
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  }
}

核心测试模式

基本测试结构
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';

describe('Calculator', () => {
  let calculator: Calculator;

  beforeEach(() => {
    calculator = new Calculator();
  });

  afterEach(() => {
    // Cleanup
  });

  it('adds two numbers correctly', () => {
    const result = calculator.add(2, 3);
    expect(result).toBe(5);
  });

  it('handles negative numbers', () => {
    expect(calculator.add(-5, 3)).toBe(-2);
  });

  it.each([
    [1, 1, 2],
    [2, 3, 5],
    [10, -5, 5],
  ])('adds %i + %i to equal %i', (a, b, expected) => {
    expect(calculator.add(a, b)).toBe(expected);
  });
});
类型安全的 TypeScript 测试
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

describe('User Service', () => {
  it('creates user with correct types', () => {
    const user: User = {
      id: 1,
      name: 'Alice',
      email: '[email protected]',
      role: 'admin',
    };

    // Type-safe assertions
    expect(user.id).toEqual(expect.any(Number));
    expect(user.name).toEqual(expect.any(String));
    expect(user.role).toMatch(/^(admin|user)$/);
  });

  it('validates user object shape', () => {
    const user = createUser('Bob', '[email protected]');

    expect(user).toMatchObject({
      id: expect.any(Number),
      name: 'Bob',
      email: '[email protected]',
    });
  });
});

使用 TypeScript 进行模拟

使用 jest.mock 模拟模块
import { jest } from '@jest/globals';
import { UserService } from './UserService';
import * as userApi from './api/userApi';

// Mock entire module
jest.mock('./api/userApi');

describe('UserService with Mocks', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('fetches user data', async () => {
    const mockUser = { id: 1, name: 'Alice', email: '[email protected]' };

    // Type-safe mock
    const mockedFetchUser = jest.mocked(userApi.fetchUser);
    mockedFetchUser.mockResolvedValue(mockUser);

    const service = new UserService();
    const user = await service.getUser(1);

    expect(mockedFetchUser).toHaveBeenCalledWith(1);
    expect(user).toEqual(mockUser);
  });
});
使用 jest.spyOn 监视方法
import { jest } from '@jest/globals';

class Logger {
  log(message: string): void {
    console.log(message);
  }

  error(message: string): void {
    console.error(message);
  }
}

describe('Logger Spy', () => {
  let logger: Logger;
  let logSpy: jest.SpyInstance;

  beforeEach(() => {
    logger = new Logger();
    logSpy = jest.spyOn(logger, 'log');
  });

  afterEach(() => {
    logSpy.mockRestore();
  });

  it('tracks method calls', () => {
    logger.log('Hello');
    logger.log('World');

    expect(logSpy).toHaveBeenCalledTimes(2);
    expect(logSpy).toHaveBeenCalledWith('Hello');
    expect(logSpy).toHaveBeenLastCalledWith('World');
  });

  it('provides custom implementation', () => {
    logSpy.mockImplementation((msg: string) => {
      console.log(`[CUSTOM] ${msg}`);
    });

    logger.log('Test');
    expect(logSpy).toHaveBeenCalledWith('Test');
  });
});
类型安全的模拟函数
import { jest } from '@jest/globals';

interface ApiResponse<T> {
  data: T;
  status: number;
}

type FetchUserFn = (id: number) => Promise<ApiResponse<User>>;

describe('Type-Safe Mocks', () => {
  it('creates typed mock function', async () => {
    const mockFetchUser = jest.fn<FetchUserFn>()
      .mockResolvedValue({
        data: { id: 1, name: 'Alice', email: '[email protected]', role: 'user' },
        status: 200,
      });

    const result = await mockFetchUser(1);

    expect(result.data.name).toBe('Alice');
    expect(result.status).toBe(200);
    expect(mockFetchUser).toHaveBeenCalledWith(1);
  });

  it('uses mock implementation', () => {
    const mockCalculate = jest.fn<(x: number, y: number) => number>()
      .mockImplementation((x, y) => x + y);

    expect(mockCalculate(5, 3)).toBe(8);
    expect(mockCalculate).toHaveBeenCalledWith(5, 3);
  });
});
模拟计时器
import { jest } from '@jest/globals';

describe('Timer Mocking', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

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

  it('fast-forwards time', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);

    jest.advanceTimersByTime(500);
    expect(callback).not.toHaveBeenCalled();

    jest.advanceTimersByTime(500);
    expect(callback).toHaveBeenCalledTimes(1);
  });

  it('runs all timers', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);
    setTimeout(callback, 2000);

    jest.runAllTimers();
    expect(callback).toHaveBeenCalledTimes(2);
  });

  it('handles intervals', () => {
    const callback = jest.fn();
    setInterval(callback, 1000);

    jest.advanceTimersByTime(3500);
    expect(callback).toHaveBeenCalledTimes(3);

    jest.clearAllTimers();
  });
});

React Testing Library + TypeScript

React 设置
npm install -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
npm install -D jest-environment-jsdom

jest.config.ts(React):

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
  },
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      tsconfig: {
        jsx: 'react-jsx',
      },
    }],
  },
};

export default config;

src/test/setup.ts:

import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import { afterEach } from '@jest/globals';

afterEach(() => {
  cleanup();
});
React 组件测试
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';

describe('Counter Component', () => {
  it('renders initial count', () => {
    render(<Counter initialCount={0} />);
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
  });

  it('increments counter on button click', async () => {
    const user = userEvent.setup();
    render(<Counter initialCount={0} />);

    const button = screen.getByRole('button', { name: /increment/i });
    await user.click(button);

    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });

  it('calls onChange callback with correct value', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<Counter initialCount={5} onChange={onChange} />);

    await user.click(screen.getByRole('button', { name: /increment/i }));

    expect(onChange).toHaveBeenCalledWith(6);
    expect(onChange).toHaveBeenCalledTimes(1);
  });

  it('disables button when max count reached', () => {
    render(<Counter initialCount={10} maxCount={10} />);

    const button = screen.getByRole('button', { name: /increment/i });
    expect(button).toBeDisabled();
  });
});
测试 Hooks
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter Hook', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter(0));
    expect(result.current.count).toBe(0);
  });

  it('increments counter', () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it('decrements counter', () => {
    const { result } = renderHook(() => useCounter(5));

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(4);
  });

  it('resets to initial value', () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.increment();
      result.current.increment();
    });

    expect(result.current.count).toBe(12);

    act(() => {
      result.current.reset();
    });

    expect(result.current.count).toBe(10);
  });
});
测试异步组件
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
import * as api from './api';

jest.mock('./api');

describe('UserProfile Async', () => {
  it('loads and displays user data', async () => {
    const mockUser = { id: 1, name: 'Alice', email: '[email protected]' };
    jest.mocked(api.fetchUser).mockResolvedValue(mockUser);

    render(<UserProfile userId={1} />);

    expect(screen.getByText('Loading...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('Alice')).toBeInTheDocument();
    });

    expect(screen.getByText('[email protected]')).toBeInTheDocument();
  });

  it('displays error on fetch failure', async () => {
    jest.mocked(api.fetchUser).mockRejectedValue(new Error('Network error'));

    render(<UserProfile userId={1} />);

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

快照测试

组件快照
import { render } from '@testing-library/react';
import { UserCard } from './UserCard';

describe('UserCard Snapshots', () => {
  it('matches snapshot for regular user', () => {
    const { container } = render(
      <UserCard
        name="Alice"
        email="[email protected]"
        role="user"
      />
    );

    expect(container.firstChild).toMatchSnapshot();
  });

  it('matches snapshot for admin user', () => {
    const { container } = render(
      <UserCard
        name="Bob"
        email="[email protected]"
        role="admin"
      />
    );

    expect(container.firstChild).toMatchSnapshot();
  });

  it('uses inline snapshot', () => {
    const user = { id: 1, name: 'Charlie', role: 'user' };

    expect(user).toMatchInlineSnapshot(`
      {
        "id": 1,
        "name": "Charlie",
        "role": "user",
      }
    `);
  });
});
更新快照
# Update all snapshots
jest --updateSnapshot
jest -u

# Update snapshots for specific test file
jest UserCard.test.tsx -u

# Interactive snapshot update
jest --watch
# Press 'u' to update failing snapshots
自定义快照序列化器
// __tests__/serializers/dateSerializer.ts
export default {
  test: (val: any) => val instanceof Date,
  print: (val: Date) => `Date(${val.toISOString()})`,
};

jest.config.ts:

const config: Config = {
  snapshotSerializers: ['<rootDir>/__tests__/serializers/dateSerializer.ts'],
};

异步测试

测试 Promise
import { fetchData, saveData } from './api';

describe('Async Operations', () => {
  it('resolves with data', async () => {
    const data = await fetchData(1);
    expect(data).toBeDefined();
    expect(data.id).toBe(1);
  });

  it('handles promise rejection', async () => {
    await expect(fetchData(-1)).rejects.toThrow('Invalid ID');
  });

  it('uses resolves matcher', async () => {
    await expect(fetchData(1)).resolves.toHaveProperty('id', 1);
  });

  it('tests multiple async operations', async () => {
    const [user, posts] = await Promise.all([
      fetchUser(1),
      fetchPosts(1),
    ]);

    expect(user.id).toBe(1);
    expect(posts).toHaveLength(expect.any(Number));
  });
});
测试回调
describe('Callback Testing', () => {
  it('calls callback with correct arguments', (done) => {
    function fetchWithCallback(id: number, callback: (data: any) => void) {
      setTimeout(() => {
        callback({ id, name: 'Test' });
      }, 100);
    }

    fetchWithCallback(1, (data) => {
      try {
        expect(data.id).toBe(1);
        expect(data.name).toBe('Test');
        done();
      } catch (error) {
        done(error);
      }
    });
  });
});

覆盖率配置

高级覆盖率设置

jest.config.ts:

const config: Config = {
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageProvider: 'v8', // or 'babel' for compatibility
  coverageReporters: ['text', 'lcov', 'html', 'json'],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.test.{ts,tsx}',
    '!src/**/__tests__/**',
    '!src/index.ts',
    '!src/types/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
    './src/core/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90,
    },
  },
  coveragePathIgnorePatterns: [
    '/node_modules/',
    '/dist/',
    '/__tests__/',
  ],
};
运行覆盖率
# Generate coverage report
npm test -- --coverage

# Coverage with watch mode
npm test -- --coverage --watch

# Coverage for specific files
npm test -- --coverage --collectCoverageFrom="src/components/**/*.tsx"

# View HTML report
open coverage/lcov-report/index.html

从 Vitest 迁移

主要区别

API 变更:

// Vitest
import { vi } from 'vitest';
const mockFn = vi.fn();
vi.spyOn(obj, 'method');

// Jest
import { jest } from '@jest/globals';
const mockFn = jest.fn();
jest.spyOn(obj, 'method');
迁移检查清单

1. 更新依赖项:

npm uninstall vitest @vitest/ui
npm install -D jest @types/jest ts-jest

2. 更新 package.json:

{
  "scripts": {
    "test": "jest",           // Was: vitest run
    "test:watch": "jest --watch"  // Was: vitest
  }
}

3. 将 vitest.config.ts 替换为 jest.config.ts:

// Old: vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
  },
});

// New: jest.config.ts
import type { Config } from 'jest';
const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  globals: {
    'ts-jest': {
      isolatedModules: true,
    },
  },
};
export default config;

4. 更新测试文件:

// Change imports
- import { vi } from 'vitest';
+ import { jest } from '@jest/globals';

// Update mocks
- vi.fn()
+ jest.fn()

- vi.spyOn()
+ jest.spyOn()

- vi.mock()
+ jest.mock()

// Timer mocks
- vi.useFakeTimers()
+ jest.useFakeTimers()

- vi.advanceTimersByTime()
+ jest.advanceTimersByTime()

5. 更新 tsconfig.json:

{
  "compilerOptions": {
    "types": ["jest", "@testing-library/jest-dom"]  // Was: vitest/globals
  }
}

Jest 与 Vitest 对比

性能

Jest:

  • 初始启动较慢(没有 HMR)
  • 默认顺序执行测试
  • 中型项目需要 1-5 秒

Vitest:

  • 基于 HMR,可即时执行
  • 默认并行执行
  • 同类项目需要 100-500ms
生态系统

Jest:

  • ✅ 70% 市场占有率
  • ✅ 成熟的生态系统(8 年以上)
  • ✅ Stack Overflow 上有更多答案
  • ✅ 企业支持更好

Vitest:

  • ✅ 现代化,采用率不断增长
  • ✅ 原生集成 Vite
  • ⚠️ 生态系统较小
  • ⚠️ 资源较少
TypeScript 支持

Jest:

  • 需要配置 ts-jest
  • 需要额外的转换步骤
  • 编译较慢

Vitest:

  • 内置 TypeScript 支持
  • 无需配置
  • 借助 Vite,速度更快
何时使用 Jest

以下情况请选择 Jest:

  • ✅ 已在使用 Jest 的现有项目
  • ✅ 要求使用经过验证工具的企业环境
  • ✅ 需要丰富生态系统支持的项目
  • ✅ 使用 Create React App 的 React 项目
  • ✅ 非 Vite 构建系统(Webpack、Rollup)

以下情况请选择 Vitest:

  • ✅ 使用现代工具链的新项目
  • ✅ 基于 Vite 的应用
  • ✅ 对性能要求较高的测试套件
  • ✅ ESM 优先的项目

最佳实践

  1. 使用 TypeScript 配置:类型安全的测试可避免运行时错误
  2. 模拟外部依赖:网络、文件系统、数据库
  3. 隔离测试:每项测试都应彼此独立
  4. 使用 describe 块:按逻辑分组相关测试
  5. 清除模拟状态:在 beforeEach 中使用 jest.clearAllMocks()
  6. 测试边界情况:空数组、null、undefined、错误
  7. 使用 .each 进行数据驱动测试:高效测试多个输入
  8. 避免测试实现细节:测试行为,而非内部结构
  9. 保持测试快速:模拟耗时操作,使用并行执行
  10. 维持覆盖率阈值:在 CI 中强制执行最低覆盖率

常见问题

❌ 测试之间未清除模拟:

// WRONG - mocks leak between tests
it('test 1', () => {
  jest.spyOn(api, 'fetch');
  // No cleanup!
});

// CORRECT
afterEach(() => {
  jest.restoreAllMocks();
});

❌ 忘记等待异步测试完成:

// WRONG - test completes before assertion
it('fetches data', () => {
  fetchData().then(data => {
    expect(data).toBeDefined();  // Never runs!
  });
});

// CORRECT
it('fetches data', async () => {
  const data = await fetchData();
  expect(data).toBeDefined();
});

❌ 使用了错误的测试环境:

// WRONG - testing DOM without jsdom
// jest.config.ts
testEnvironment: 'node',  // Can't test React!

// CORRECT
testEnvironment: 'jsdom',

❌ 未为模拟使用 TypeScript 类型:

// WRONG - no type safety
const mockFn = jest.fn();

// CORRECT
const mockFn = jest.fn<(id: number) => Promise<User>>();

资源

相关技能

使用 Jest 时,可考虑以下互补技能:

  • typescript-core:高级 TypeScript 模式、tsconfig 优化和类型安全
  • react:使用 Testing Library 测试 React 组件的模式
  • vitest:基于 Vite 的现代替代方案,性能更快、执行速度更高
TypeScript 类型安全速查参考(内联,便于独立使用)
// Type-safe test helpers with generics
function createMockUser<T extends Partial<User>>(overrides: T): User & T {
  return {
    id: 1,
    name: 'Test User',
    email: '[email protected]',
    ...overrides
  };
}

// Usage with type inference
const adminUser = createMockUser({ role: 'admin' });
// Type: User & { role: string }

// Type-safe mock functions
const mockFetch = jest.fn<typeof fetch>();
mockFetch.mockResolvedValue(new Response('{}'));

// Const type parameters for literal types
const createConfig = <const T extends Record<string, unknown>>(config: T): T => config;
const testConfig = createConfig({ environment: 'test', debug: true });
// Type: { environment: "test"; debug: true } (literals preserved)
React 测试模式速查(内联,便于独立使用)
// React Testing Library with Jest
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

// Component testing pattern
describe('UserProfile', () => {
  it('should display user information', () => {
    const user = { id: 1, name: 'Alice', email: '[email protected]' };
    render(<UserProfile user={user} />);

    expect(screen.getByText('Alice')).toBeInTheDocument();
    expect(screen.getByText('[email protected]')).toBeInTheDocument();
  });

  it('should handle user interactions', async () => {
    const onSubmit = jest.fn();
    render(<UserForm onSubmit={onSubmit} />);

    // User interactions
    await userEvent.type(screen.getByLabelText('Name'), 'Bob');
    await userEvent.click(screen.getByRole('button', { name: 'Submit' }));

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({ name: 'Bob' });
    });
  });
});

// Hook testing
import { renderHook, act } from '@testing-library/react';

test('useCounter hook', () => {
  const { result } = renderHook(() => useCounter(0));

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

// Context and Provider testing
const wrapper = ({ children }: { children: React.ReactNode }) => (
  <AuthProvider>{children}</AuthProvider>
);

test('useAuth hook with context', () => {
  const { result } = renderHook(() => useAuth(), { wrapper });
  expect(result.current.user).toBeDefined();
});
Vitest 快速对比(内联,便于独立使用)

何时选择 Vitest 而非 Jest:

  • 新建 Vite/Vite 构建的项目(使用 Turbopack 的 Next.js、SvelteKit)
  • 需要更快的测试执行速度(快 10–100 倍)
  • 采用 ESM 优先架构
  • 需要用于测试的热模块替换

何时继续使用 Jest:

  • 已配置 Jest 的大型现有代码库
  • 已建立 Jest 工作流的企业环境
  • 需要成熟的生态系统和丰富的插件
  • 使用 Create React App 的 React 应用(默认配置 Jest)

迁移代码片段(Jest → Vitest):

// Jest: import from '@testing-library/jest-dom'
import '@testing-library/jest-dom';

// Vitest: import from vitest globals
import { expect, test, describe } from 'vitest';
import { screen } from '@testing-library/react';

// Most Jest syntax works in Vitest unchanged
test('component renders', () => {
  render(<Component />);
  expect(screen.getByText('Hello')).toBeTruthy();
});

[如果同时部署,完整的 TypeScript、React 和 Vitest 模式可在相应技能中找到]

总结

  • Jest 是行业标准,市场占有率达 70%
  • TypeScript 支持:通过 ts-jest 实现全面的类型安全
  • 一体化解决方案:测试运行器、断言、模拟和覆盖率
  • 集成 React Testing Library,用于组件测试
  • 成熟的生态系统:拥有丰富的工具和支持
  • 快照测试:用于 UI 回归测试
  • 支持从 Vitest 迁移,提供兼容的 API
  • 最适合:现有项目、企业环境、React 应用、旧版项目支持
  • 权衡:比 Vitest 慢,但更成熟且支持更广泛

评论

支持 Markdown