Jest 调试技巧 #

调试概述 #

测试调试是解决测试问题的重要技能,掌握调试技巧可以快速定位问题。

text
┌─────────────────────────────────────────────────────────────┐
│                    调试方法                                  │
├─────────────────────────────────────────────────────────────┤
│  1. 日志输出 - console.log 等基本方法                        │
│  2. 断点调试 - 使用调试器暂停执行                            │
│  3. 错误分析 - 解读错误消息                                  │
│  4. 工具集成 - VS Code 等编辑器集成                          │
│  5. 快照调试 - 查看测试快照                                  │
└─────────────────────────────────────────────────────────────┘

日志输出 #

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.log('JSON:', JSON.stringify(user, null, 2));
  
  expect(user.name).toBe('John');
});

console.table #

javascript
test('debug with console.table', () => {
  const users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
  ];
  
  console.table(users);
  
  expect(users).toHaveLength(2);
});

console.dir #

javascript
test('debug with console.dir', () => {
  const obj = { a: { b: { c: 1 } } };
  
  console.dir(obj, { depth: null });
  
  expect(obj.a.b.c).toBe(1);
});

使用 debug 包 #

javascript
import debug from 'debug';

const log = debug('test:user');

test('debug with debug package', () => {
  const user = { name: 'John' };
  log('user data: %o', user);
  
  expect(user.name).toBe('John');
});
bash
# 启用 debug 输出
DEBUG=test:* jest

断点调试 #

Node 调试器 #

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

# 或使用 node inspect
node --inspect node_modules/.bin/jest --runInBand --no-cache

Chrome DevTools #

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

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

VS Code 调试 #

json
// .vscode/launch.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",
      "windows": {
        "program": "${workspaceFolder}/node_modules/jest/bin/jest"
      }
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Debug Current File",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "${fileBasenameNoExtension}",
        "--config",
        "jest.config.js"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

调试特定测试 #

json
{
  "type": "node",
  "request": "launch",
  "name": "Jest Debug Specific Test",
  "program": "${workspaceFolder}/node_modules/.bin/jest",
  "args": [
    "--runInBand",
    "--no-cache",
    "--testNamePattern=should add user"
  ],
  "console": "integratedTerminal"
}

错误分析 #

解读错误消息 #

javascript
// 典型错误消息
// ● Test suite failed to run
// 
// TypeError: Cannot read property 'name' of undefined
// 
//   at Object.<anonymous> (user.test.js:5:18)
//   at processTicksAndRejections (internal/process/task_queues.js:95:5)

// 分析步骤:
// 1. 错误类型: TypeError
// 2. 错误消息: Cannot read property 'name' of undefined
// 3. 错误位置: user.test.js:5:18

增强错误输出 #

javascript
test('better error messages', () => {
  const result = calculate(1, 2);
  
  // ❌ 不好的做法
  expect(result).toBe(3);
  
  // ✅ 好的做法 - 提供上下文
  expect(result).withContext('calculate(1, 2) should return 3').toBe(3);
  
  // ✅ 或使用自定义消息
  if (result !== 3) {
    throw new Error(`Expected 3, got ${result}`);
  }
});

使用 try-catch #

javascript
test('debug with try-catch', () => {
  try {
    const result = riskyOperation();
    expect(result).toBe('expected');
  } catch (error) {
    console.log('Error caught:', error);
    console.log('Stack trace:', error.stack);
    throw error; // 重新抛出以使测试失败
  }
});

详细断言失败 #

javascript
test('detailed assertion', () => {
  const user = { name: 'John', age: 30 };
  
  // 使用 toEqual 而不是 toBe
  expect(user).toEqual({
    name: 'John',
    age: 30,
  });
  
  // 使用 toMatchObject 部分匹配
  expect(user).toMatchObject({
    name: 'John',
  });
});

调试异步代码 #

异步错误追踪 #

javascript
test('debug async', async () => {
  try {
    const result = await fetchData();
    console.log('Result:', result);
    expect(result).toBeDefined();
  } catch (error) {
    console.log('Async error:', error);
    throw error;
  }
});

Promise 拒绝调试 #

javascript
test('debug promise rejection', async () => {
  await expect(fetchData()).rejects.toThrow();
  
  // 或手动捕获
  try {
    await fetchData();
  } catch (error) {
    console.log('Rejection reason:', error);
  }
});

定时器调试 #

javascript
test('debug timers', () => {
  jest.useFakeTimers();
  
  const callback = jest.fn();
  setTimeout(callback, 1000);
  
  console.log('Timer state:', jest.getTimerCount());
  
  jest.advanceTimersByTime(500);
  console.log('After 500ms:', callback.mock.calls);
  
  jest.advanceTimersByTime(500);
  console.log('After 1000ms:', callback.mock.calls);
  
  jest.useRealTimers();
});

Mock 调试 #

查看 Mock 状态 #

javascript
test('debug mock', () => {
  const mockFn = jest.fn();
  
  mockFn('hello');
  mockFn('world', 123);
  
  // 查看 Mock 调用
  console.log('Calls:', mockFn.mock.calls);
  console.log('Call count:', mockFn.mock.calls.length);
  console.log('First call:', mockFn.mock.calls[0]);
  console.log('Last call:', mockFn.mock.calls[mockFn.mock.calls.length - 1]);
  
  // 查看返回值
  console.log('Results:', mockFn.mock.results);
});

Mock 实现调试 #

javascript
test('debug mock implementation', () => {
  const mockFn = jest.fn((...args) => {
    console.log('Mock called with:', args);
    return args.reduce((a, b) => a + b, 0);
  });
  
  const result = mockFn(1, 2, 3);
  console.log('Mock result:', result);
  
  expect(result).toBe(6);
});

快照调试 #

查看快照差异 #

bash
# 更新快照并查看差异
jest --updateSnapshot --verbose

# 使用 git diff 查看
git diff -- "*.snap"

内联快照调试 #

javascript
test('inline snapshot debug', () => {
  const data = { name: 'John' };
  
  // 第一次运行会生成快照
  expect(data).toMatchInlineSnapshot();
  
  // 查看生成的快照
  console.log('Snapshot:', expect.getState().currentTestName);
});

测试隔离调试 #

只运行一个测试 #

javascript
// 使用 only
test.only('this test only', () => {
  // 测试代码
});

// 或使用命令行
jest --testNamePattern="this test only"

跳过其他测试 #

javascript
// 跳过其他测试
test.skip('skipped test', () => {});

// 或使用 describe.skip
describe.skip('skipped suite', () => {});

调试工具 #

jest-diff #

javascript
const { diff } = require('jest-diff');

test('show diff', () => {
  const a = { name: 'John', age: 30 };
  const b = { name: 'Jane', age: 25 };
  
  console.log(diff(a, b));
});

pretty-format #

javascript
const prettyFormat = require('pretty-format');

test('pretty print', () => {
  const user = { name: 'John', nested: { a: 1, b: 2 } };
  
  console.log(prettyFormat(user));
});

常见问题调试 #

测试超时 #

javascript
test('debug timeout', async () => {
  // 增加超时时间
}, 30000);

// 或配置全局超时
// jest.config.js
module.exports = {
  testTimeout: 30000,
};

内存泄漏 #

bash
# 检测内存泄漏
jest --detectOpenHandles --forceExit

# 查看内存使用
jest --logHeapUsage

测试不执行 #

javascript
// 检查测试匹配
console.log('Test file:', __filename);

// 检查测试名称
test('my test', () => {
  console.log('Test executed');
});

调试最佳实践 #

1. 使用描述性消息 #

javascript
test('should calculate total correctly', () => {
  const cart = new Cart();
  cart.addItem({ price: 100 });
  
  expect(cart.total).toBe(100);
});

2. 分步调试 #

javascript
test('step by step', () => {
  // 步骤 1
  const user = createUser('John');
  console.log('Step 1 - User created:', user);
  
  // 步骤 2
  user.setEmail('john@example.com');
  console.log('Step 2 - Email set:', user.email);
  
  // 步骤 3
  const saved = saveUser(user);
  console.log('Step 3 - User saved:', saved);
  
  expect(saved.id).toBeDefined();
});

3. 使用调试器语句 #

javascript
test('debugger statement', () => {
  const data = { name: 'John' };
  
  debugger; // 在此处暂停
  
  expect(data.name).toBe('John');
});

下一步 #

现在你已经掌握了 Jest 调试技巧,接下来学习 扩展与插件 扩展 Jest 功能!

最后更新:2026-03-28