Jest 调试技巧 #

调试概述 #

测试调试是开发过程中的重要环节,掌握有效的调试技巧可以快速定位和解决问题。

text
┌─────────────────────────────────────────────────────────────┐
│                    调试方法                                  │
├─────────────────────────────────────────────────────────────┤
│  1. 日志输出 - console.log、debugger                        │
│  2. 断点调试 - VS Code、Chrome DevTools                     │
│  3. 详细输出 --verbose、--detectOpenHandles                 │
│  4. 错误分析 - 堆栈跟踪、错误消息                            │
└─────────────────────────────────────────────────────────────┘

日志输出 #

console.log #

javascript
test('debug with console.log', () => {
  const user = { name: 'John', age: 30 };
  
  console.log('user:', user);
  console.log('user.name:', user.name);
  console.table(user);
  
  expect(user.name).toBe('John');
});

console 输出控制 #

bash
# 显示所有 console 输出
jest --verbose

# 隐藏 console 输出
jest --silent

使用 console.group #

javascript
test('grouped logs', () => {
  console.group('Test Details');
  console.log('Step 1: Setup');
  console.log('Step 2: Execute');
  console.log('Step 3: Verify');
  console.groupEnd();
});

使用 util.inspect #

javascript
const util = require('util');

test('deep inspect', () => {
  const obj = { a: { b: { c: { d: 1 } } } };
  console.log(util.inspect(obj, { depth: null, colors: true }));
});

断点调试 #

debugger 语句 #

javascript
test('debug with debugger', () => {
  const value = calculate(1, 2);
  
  debugger; // 执行会在这里暂停
  
  expect(value).toBe(3);
});
bash
# 运行时启用调试
node --inspect-brk node_modules/.bin/jest --runInBand

VS Code 调试 #

创建 .vscode/launch.json

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Debug",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "--runInBand",
        "--no-cache"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Debug Current File",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "${fileBasenameNoExtension}",
        "--runInBand",
        "--no-cache"
      ],
      "console": "integratedTerminal"
    }
  ]
}

Chrome DevTools 调试 #

bash
# 启动调试模式
node --inspect-brk node_modules/.bin/jest --runInBand

# 打开 Chrome
# chrome://inspect
# 点击 "Open dedicated DevTools for Node"

详细输出 #

–verbose #

bash
# 显示详细输出
jest --verbose

输出示例:

text
PASS  src/utils/math.test.js
  Calculator
    add
      ✓ should add two numbers (2 ms)
      ✓ should add negative numbers (1 ms)
    subtract
      ✓ should subtract two numbers
      ✓ should handle zero

–detectOpenHandles #

bash
# 检测未关闭的句柄
jest --detectOpenHandles

–forceExit #

bash
# 强制退出
jest --forceExit

–detectLeaks #

bash
# 检测内存泄漏
jest --detectLeaks

错误分析 #

理解错误消息 #

javascript
test('error example', () => {
  const result = { a: 1, b: 2 };
  expect(result).toEqual({ a: 1, b: 3 });
});

错误输出:

text
expect(received).toEqual(expected)

Expected: {"a": 1, "b": 3}
Received: {"a": 1, "b": 2}

Difference:
- Expected
+ Received

  Object {
    "a": 1,
-   "b": 3,
+   "b": 2,
  }

使用 .toThrow #

javascript
test('error matching', () => {
  expect(() => throwError()).toThrow('Error message');
  expect(() => throwError()).toThrow(Error);
  expect(() => throwError()).toThrow(/error/i);
});

捕获异步错误 #

javascript
test('async error', async () => {
  await expect(asyncError()).rejects.toThrow('Async error');
});

// 或使用 try/catch
test('async error with try/catch', async () => {
  try {
    await asyncError();
  } catch (error) {
    expect(error.message).toBe('Async error');
  }
});

常见问题 #

测试超时 #

javascript
// 问题:测试超时
test('timeout test', async () => {
  await slowOperation();
}, 5000); // 增加超时时间

// 或在配置中设置
// jest.config.js
module.exports = {
  testTimeout: 10000,
};

Mock 不生效 #

javascript
// 问题:Mock 在导入之后
import { fetchData } from './api';
jest.mock('./api'); // 太晚了

// 解决方案:Mock 必须在导入之前
jest.mock('./api');
import { fetchData } from './api';

异步测试提前结束 #

javascript
// 问题:测试在异步完成前结束
test('bad async', () => {
  fetchData().then(data => {
    expect(data).toBe('expected');
  });
  // 没有 return 或 await
});

// 解决方案
test('good async', async () => {
  const data = await fetchData();
  expect(data).toBe('expected');
});

定时器问题 #

javascript
// 问题:定时器不执行
test('timer issue', () => {
  setTimeout(() => {
    expect(true).toBe(true);
  }, 1000);
  // 测试立即结束
});

// 解决方案
test('timer solution', () => {
  jest.useFakeTimers();

  const callback = jest.fn();
  setTimeout(callback, 1000);

  jest.advanceTimersByTime(1000);
  expect(callback).toHaveBeenCalled();

  jest.useRealTimers();
});

DOM 元素找不到 #

javascript
// 问题:元素找不到
test('element not found', () => {
  render(<Component />);
  screen.getByText('Text'); // 抛出错误
});

// 解决方案:使用 debug
test('debug DOM', () => {
  render(<Component />);
  screen.debug(); // 打印 DOM 结构
  screen.getByText('Text');
});

快照不匹配 #

javascript
// 问题:快照不匹配
// 1. 检查变更是否正确
// 2. 如果正确,更新快照
// jest -u

调试工具 #

jest-dom debug #

javascript
import { screen } from '@testing-library/react';

test('debug DOM', () => {
  render(<Component />);
  
  // 打印整个 DOM
  screen.debug();
  
  // 打印特定元素
  screen.debug(screen.getByRole('button'));
  
  // 打印多个元素
  screen.debug(screen.getAllByRole('listitem'));
});

prettyDOM #

javascript
import { prettyDOM } from '@testing-library/react';

test('pretty DOM', () => {
  const { container } = render(<Component />);
  
  console.log(prettyDOM(container));
  console.log(prettyDOM(container.firstChild));
});

logTestingPlaygroundURL #

javascript
import { logTestingPlaygroundURL } from '@testing-library/react';

test('playground URL', () => {
  render(<Component />);
  
  // 生成 Testing Playground URL
  logTestingPlaygroundURL();
});

调试模式 #

单独运行测试 #

bash
# 运行单个文件
jest sum.test.js

# 运行匹配模式的测试
jest --testNamePattern="add"

# 使用 only
test.only('only this test runs', () => {});

串行运行 #

bash
# 串行运行,更容易调试
jest --runInBand

禁用并行 #

javascript
// jest.config.js
module.exports = {
  maxWorkers: 1,
};

错误处理 #

全局错误处理 #

javascript
// jest.setup.js
process.on('unhandledRejection', (reason, promise) => {
  console.log('Unhandled Rejection:', reason);
});

process.on('uncaughtException', (error) => {
  console.log('Uncaught Exception:', error);
});

测试错误处理 #

javascript
describe('error handling', () => {
  let consoleError;

  beforeEach(() => {
    consoleError = console.error;
    console.error = jest.fn();
  });

  afterEach(() => {
    console.error = consoleError;
  });

  test('handles error', () => {
    // 测试代码
    expect(console.error).toHaveBeenCalled();
  });
});

调试技巧清单 #

通用调试流程 #

text
1. 理解错误消息
   ├── 阅读错误类型
   ├── 查看期望值和实际值
   └── 检查堆栈跟踪

2. 隔离问题
   ├── 只运行失败的测试
   ├── 使用 test.only
   └── 移除无关代码

3. 添加日志
   ├── console.log 关键变量
   ├── screen.debug() DOM 结构
   └── 使用 debugger 断点

4. 检查常见问题
   ├── Mock 是否正确
   ├── 异步是否处理
   ├── 定时器是否 Mock
   └── 状态是否隔离

5. 解决问题
   ├── 修复代码或测试
   ├── 更新快照(如果需要)
   └── 验证修复

调试命令速查 #

bash
# 详细输出
jest --verbose

# 串行运行
jest --runInBand

# 检测未关闭句柄
jest --detectOpenHandles

# 检测内存泄漏
jest --detectLeaks

# 强制退出
jest --forceExit

# 清除缓存
jest --clearCache

# 显示配置
jest --showConfig

# 调试模式
node --inspect-brk node_modules/.bin/jest --runInBand

最佳实践 #

1. 先理解错误再修复 #

javascript
// ❌ 不好的做法 - 盲目修改
// 随意更改代码

// ✅ 好的做法 - 理解后修复
// 1. 阅读错误消息
// 2. 理解期望值和实际值
// 3. 找出差异原因
// 4. 针对性修复

2. 使用最小化测试 #

javascript
// ❌ 不好的做法 - 在复杂测试中调试
test('complex test', () => {
  // 很多代码...
  // 错误发生在这里
});

// ✅ 好的做法 - 创建最小化测试
test('minimal test', () => {
  const result = problematicFunction();
  expect(result).toBe('expected');
});

3. 保持测试独立 #

javascript
// ❌ 不好的做法 - 测试依赖
let sharedState;

test('test 1', () => {
  sharedState = 'value';
});

test('test 2', () => {
  // 依赖 test 1
  expect(sharedState).toBe('value');
});

// ✅ 好的做法 - 测试独立
test('test 1', () => {
  const state = setup();
  expect(state).toBeDefined();
});

test('test 2', () => {
  const state = setup();
  expect(state).toBeDefined();
});

下一步 #

现在你已经掌握了 Jest 调试技巧,接下来学习 React 测试 实战 React 组件测试!

最后更新:2026-03-28