mcpField
J
jest-typescript

jest-typescript

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

关于 jest-typescript

jest-typescript 是为 TypeScript 项目配置 Jest 测试的技能,涵盖测试模式、React 测试、模拟、快照和覆盖率报告。

主要功能

  1. 01使用 ts-jest 配置 Jest
  2. 02编写类型安全的测试
  3. 03模拟模块、方法和计时器
  4. 04测试 React 组件与 Hook
  5. 05生成快照和覆盖率报告
  6. 06将测试从 Vitest 迁移

适用场景

  1. 01在 TypeScript 项目中配置 Jest
  2. 02使用 Testing Library 测试 React 组件
  3. 03测量覆盖率或替换 Vitest

安装

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

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

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

附带文件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