Jest配置Skill jest-configuration

Jest 配置技能用于管理和优化 Jest 测试框架的设置,包括安装、配置文件、模块解析、项目组织和测试环境,适用于 JavaScript 和 TypeScript 项目。它帮助开发者创建高效的测试套件,支持多项目配置、覆盖率分析,并提供最佳实践指导,以提升测试质量和开发效率。关键词:Jest 配置,测试环境,TypeScript 测试,覆盖率,模块解析,项目组织。

测试 0 次安装 0 次浏览 更新于 3/25/2026

名称: jest-configuration 用户不可调用: false 描述: 当需要 Jest 配置、设置文件、模块解析和项目组织以优化测试环境时使用。 允许工具: [读取, 写入, 编辑, Bash, Glob, Grep]

Jest 配置

掌握 Jest 配置、设置文件、模块解析和项目组织,以创建优化的测试环境。此技能涵盖了为现代 JavaScript 和 TypeScript 项目配置 Jest 的所有方面,从基本设置到高级的多项目配置。

安装和设置

基本安装

# npm
npm install --save-dev jest

# yarn
yarn add --dev jest

# pnpm
pnpm add -D jest

TypeScript 支持

npm install --save-dev @types/jest ts-jest

React 测试库

npm install --save-dev @testing-library/react @testing-library/jest-dom

配置文件

jest.config.js(推荐)

/** @type {import('jest').Config} */
module.exports = {
  // 测试环境
  testEnvironment: 'node', // 或 'jsdom' 用于类似浏览器的环境

  // 测试的根目录
  roots: ['<rootDir>/src'],

  // 要考虑的文件扩展名
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],

  // 测试匹配模式
  testMatch: [
    '**/__tests__/**/*.[jt]s?(x)',
    '**/?(*.)+(spec|test).[jt]s?(x)'
  ],

  // 测试前转换文件
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
    '^.+\\.jsx?$': 'babel-jest'
  },

  // 覆盖率配置
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{js,jsx,ts,tsx}',
    '!src/**/__tests__/**'
  ],

  // 覆盖率阈值
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },

  // 设置文件
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],

  // 导入的模块名称映射
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js'
  },

  // 在测试之间清除模拟
  clearMocks: true,

  // 在测试之间恢复模拟
  restoreMocks: true,

  // 详细输出
  verbose: true
};

TypeScript 配置(jest.config.ts)

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  transform: {
    '^.+\\.ts$': ['ts-jest', {
      tsconfig: {
        esModuleInterop: true,
        allowSyntheticDefaultImports: true
      }
    }]
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/__tests__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

export default config;

Package.json 配置

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node"
  }
}

设置文件

jest.setup.js

// 全局测试设置
import '@testing-library/jest-dom';

// 设置全局测试超时
jest.setTimeout(10000);

// 模拟环境变量
process.env.NODE_ENV = 'test';
process.env.API_URL = 'http://localhost:3000';

// 全局 before/after 钩子
beforeAll(() => {
  // 在所有测试之前运行的设置代码
  console.log('开始测试套件');
});

afterAll(() => {
  // 在所有测试之后运行的清理代码
  console.log('测试套件完成');
});

// 模拟控制台方法以减少噪音
global.console = {
  ...console,
  error: jest.fn(),
  warning: jest.fn()
};

// 自定义匹配器
expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () =>
          `expected ${received} not to be within range ${floor} - ${ceiling}`,
        pass: true
      };
    } else {
      return {
        message: () =>
          `expected ${received} to be within range ${floor} - ${ceiling}`,
        pass: false
      };
    }
  }
});

React 测试设置

import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';

// 配置测试库
configure({ testIdAttribute: 'data-testid' });

// 模拟 window.matchMedia
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn()
  }))
});

// 模拟 IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
  constructor() {}
  disconnect() {}
  observe() {}
  takeRecords() {
    return [];
  }
  unobserve() {}
};

模块解析

路径映射

// jest.config.js
module.exports = {
  moduleNameMapper: {
    // 别名映射
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1',
    '^@hooks/(.*)$': '<rootDir>/src/hooks/$1',
    '^@services/(.*)$': '<rootDir>/src/services/$1',

    // 样式模拟
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',

    // 资产模拟
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
    '\\.(woff|woff2|eot|ttf|otf)$': '<rootDir>/__mocks__/fileMock.js'
  },

  // 模块目录
  modulePaths: ['<rootDir>/src'],

  // 忽略的模块路径
  modulePathIgnorePatterns: [
    '<rootDir>/dist/',
    '<rootDir>/build/',
    '<rootDir>/coverage/'
  ]
};

文件模拟

// __mocks__/fileMock.js
module.exports = 'test-file-stub';
// __mocks__/styleMock.js
module.exports = {};

多项目配置

Monorepo 设置

// jest.config.js
module.exports = {
  projects: [
    {
      displayName: 'client',
      testEnvironment: 'jsdom',
      testMatch: ['<rootDir>/packages/client/**/*.test.{js,jsx,ts,tsx}'],
      setupFilesAfterEnv: ['<rootDir>/packages/client/jest.setup.js']
    },
    {
      displayName: 'server',
      testEnvironment: 'node',
      testMatch: ['<rootDir>/packages/server/**/*.test.{js,ts}'],
      setupFilesAfterEnv: ['<rootDir>/packages/server/jest.setup.js']
    },
    {
      displayName: 'shared',
      testEnvironment: 'node',
      testMatch: ['<rootDir>/packages/shared/**/*.test.{js,ts}']
    }
  ],
  coverageDirectory: '<rootDir>/coverage',
  collectCoverageFrom: [
    'packages/*/src/**/*.{js,jsx,ts,tsx}',
    '!**/*.d.ts',
    '!**/node_modules/**'
  ]
};

项目特定配置

// packages/client/jest.config.js
module.exports = {
  displayName: 'client',
  preset: '../../jest.preset.js',
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@babel/preset-react'] }]
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
};

环境配置

自定义测试环境

// custom-environment.js
const NodeEnvironment = require('jest-environment-node').default;

class CustomEnvironment extends NodeEnvironment {
  constructor(config, context) {
    super(config, context);
    this.testPath = context.testPath;
  }

  async setup() {
    await super.setup();
    // 自定义设置逻辑
    this.global.testEnvironmentSetup = true;
  }

  async teardown() {
    // 自定义清理逻辑
    delete this.global.testEnvironmentSetup;
    await super.teardown();
  }

  getVmContext() {
    return super.getVmContext();
  }
}

module.exports = CustomEnvironment;
// jest.config.js
module.exports = {
  testEnvironment: './custom-environment.js'
};

转换配置

Babel 转换

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript',
    '@babel/preset-react'
  ],
  plugins: [
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-transform-runtime'
  ]
};

自定义转换器

// custom-transformer.js
const { createTransformer } = require('babel-jest');

module.exports = createTransformer({
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
    '@babel/preset-typescript'
  ],
  plugins: ['babel-plugin-transform-import-meta']
});

最佳实践

  1. 使用 TypeScript 配置文件以确保类型安全 - 利用 TypeScript 配置文件在编译时捕获配置错误
  2. __tests__ 目录中组织测试 - 将测试靠近源文件,以便更好地发现
  3. 设置适当的覆盖率阈值 - 定义现实的覆盖率目标,平衡全面性和可维护性
  4. 使用设置文件进行全局配置 - 集中常见设置逻辑,避免在测试文件中重复
  5. 配置模块名称映射以使导入更清晰 - 使用路径别名,使测试导入更易读和可维护
  6. 分离环境特定的配置 - 为 Node 和浏览器环境使用不同的配置
  7. 在测试之间清除模拟 - 通过自动重置模拟来防止测试污染
  8. 为 monorepo 设置使用项目 - 利用多项目配置实现更好的组织
  9. 配置适当的超时 - 为异步操作设置现实的超时,以防止误报失败
  10. 在开发过程中使用详细输出 - 启用详细日志记录,以帮助调试测试失败

常见陷阱

  1. 忘记安装必需的依赖 - 缺少 @types/jest 或测试库会导致隐晦的错误
  2. 模块解析路径不正确 - 错误配置的 moduleNameMapper 导致模块未找到错误
  3. 未在测试之间清除模拟 - 共享的模拟状态导致测试不稳定和假阳性
  4. 覆盖率阈值过于严格 - 不现实的覆盖率目标会阻碍测试并减慢开发
  5. 缺少转换配置 - 文件未转换导致测试中出现语法错误
  6. 全局和本地配置冲突 - Package.json 配置意外覆盖 jest.config.js
  7. 未正确配置测试环境 - 使用错误的环境(node 与 jsdom)导致未定义错误
  8. 忽略 setupFilesAfterEnv - 缺少全局设置导致每个测试文件中重复的样板代码
  9. 未处理 CSS/资产导入 - 未模拟的样式导入在 Node 环境中破坏测试
  10. 测试匹配模式不正确 - 由于模式不匹配,未发现测试

何时使用此技能

  • 在新项目中从头设置 Jest
  • 从另一个测试框架迁移到 Jest
  • 为 TypeScript 项目配置 Jest
  • 为 monorepos 设置测试基础设施
  • 为 CI/CD 管道优化 Jest 配置
  • 调试测试中的模块解析问题
  • 配置自定义测试环境
  • 设置路径别名以使导入更清晰
  • 为特殊文件类型实现自定义转换器
  • 为团队建立覆盖率要求