Jest 基础测试 #

测试的基本结构 #

test 函数 #

test 是 Jest 中最基础的测试函数,用于定义一个测试用例:

javascript
test('测试描述', () => {
  // 测试代码
});

第一个测试 #

javascript
// sum.js
function sum(a, b) {
  return a + b;
}

module.exports = sum;

// sum.test.js
const sum = require('./sum');

test('1 + 2 应该等于 3', () => {
  expect(sum(1, 2)).toBe(3);
});

test('0 + 0 应该等于 0', () => {
  expect(sum(0, 0)).toBe(0);
});

test('负数相加', () => {
  expect(sum(-1, -2)).toBe(-3);
});

it 函数 #

ittest 的别名,功能完全相同:

javascript
it('should add two numbers', () => {
  expect(sum(1, 2)).toBe(3);
});

// test 和 it 可以混用
test('test 函数', () => {
  expect(true).toBe(true);
});

it('it 函数', () => {
  expect(true).toBe(true);
});

组织测试 #

describe 块 #

使用 describe 将相关测试分组:

javascript
describe('Calculator', () => {
  describe('add', () => {
    test('adds positive numbers', () => {
      expect(add(1, 2)).toBe(3);
    });

    test('adds negative numbers', () => {
      expect(add(-1, -2)).toBe(-3);
    });
  });

  describe('subtract', () => {
    test('subtracts positive numbers', () => {
      expect(subtract(5, 3)).toBe(2);
    });

    test('subtracts negative numbers', () => {
      expect(subtract(-5, -3)).toBe(-2);
    });
  });
});

嵌套 describe #

javascript
describe('Math', () => {
  describe('Calculator', () => {
    describe('add', () => {
      test('adds two numbers', () => {
        expect(add(1, 2)).toBe(3);
      });
    });

    describe('multiply', () => {
      test('multiplies two numbers', () => {
        expect(multiply(2, 3)).toBe(6);
      });
    });
  });
});

测试输出结构 #

text
PASS  math.test.js
  Math
    Calculator
      add
        ✓ adds two numbers (2 ms)
      multiply
        ✓ multiplies two numbers (1 ms)

测试命名规范 #

好的命名 #

javascript
// ✅ 描述行为
test('should return sum of two numbers', () => {});
test('should throw error when input is invalid', () => {});
test('should return empty array when no items found', () => {});

// ✅ 描述预期结果
test('returns true when user is logged in', () => {});
test('returns false when password is incorrect', () => {});

// ✅ 使用中文描述(团队约定)
test('用户登录成功后应该返回 token', () => {});
test('密码错误时应该抛出异常', () => {});

不好的命名 #

javascript
// ❌ 太模糊
test('test1', () => {});
test('works', () => {});

// ❌ 技术细节而非行为
test('calls function', () => {});
test('returns value', () => {});

// ❌ 太长
test('should return the correct sum when adding two positive numbers together', () => {});

跳过测试 #

skip 方法 #

javascript
// 跳过单个测试
test.skip('这个测试被跳过', () => {
  expect(true).toBe(false);
});

// 跳过 describe 块
describe.skip('Calculator', () => {
  test('adds numbers', () => {
    expect(add(1, 2)).toBe(3);
  });
});

// 使用 it.skip
it.skip('skipped test', () => {
  expect(true).toBe(true);
});

条件跳过 #

javascript
const shouldRun = process.env.RUN_SLOW_TESTS === 'true';

test.skipIf(!shouldRun)('slow test', () => {
  // 耗时测试
});

仅运行特定测试 #

javascript
// 只运行这一个测试
test.only('只运行这个测试', () => {
  expect(true).toBe(true);
});

// 只运行这个 describe 块
describe.only('Calculator', () => {
  test('adds numbers', () => {
    expect(add(1, 2)).toBe(3);
  });
});

// 其他测试都会被跳过
test('这个测试会被跳过', () => {
  expect(true).toBe(true);
});

测试结构最佳实践 #

AAA 模式 #

Arrange(准备)- Act(执行)- Assert(断言):

javascript
test('should add item to cart', () => {
  // Arrange - 准备测试数据
  const cart = new Cart();
  const item = { id: 1, name: 'Product', price: 100 };

  // Act - 执行被测试的操作
  cart.addItem(item);

  // Assert - 验证结果
  expect(cart.items).toHaveLength(1);
  expect(cart.items[0]).toEqual(item);
  expect(cart.total).toBe(100);
});

单一职责 #

每个测试只验证一个行为:

javascript
// ✅ 好的做法 - 每个测试一个断言点
test('should calculate total correctly', () => {
  const cart = new Cart();
  cart.addItem({ price: 100 });
  cart.addItem({ price: 200 });
  expect(cart.total).toBe(300);
});

test('should apply discount correctly', () => {
  const cart = new Cart();
  cart.addItem({ price: 100 });
  cart.applyDiscount(0.1);
  expect(cart.total).toBe(90);
});

// ❌ 不好的做法 - 一个测试验证多个行为
test('cart operations', () => {
  const cart = new Cart();
  cart.addItem({ price: 100 });
  expect(cart.items).toHaveLength(1);
  expect(cart.total).toBe(100);
  cart.applyDiscount(0.1);
  expect(cart.total).toBe(90);
});

测试隔离 #

每个测试应该独立,不依赖其他测试:

javascript
// ✅ 好的做法 - 每个测试独立
describe('User', () => {
  let user;

  beforeEach(() => {
    user = new User('John');  // 每个测试都有新的 user
  });

  test('should have correct name', () => {
    expect(user.name).toBe('John');
  });

  test('should update name', () => {
    user.setName('Jane');
    expect(user.name).toBe('Jane');
  });
});

// ❌ 不好的做法 - 测试之间有依赖
describe('User', () => {
  let user = new User('John');

  test('should have correct name', () => {
    expect(user.name).toBe('John');
  });

  test('should update name', () => {
    // 依赖上一个测试的状态
    user.setName('Jane');
    expect(user.name).toBe('Jane');
  });
});

测试文件组织 #

文件命名 #

text
src/
├── utils/
│   ├── math.js
│   ├── math.test.js      # 同目录放置测试文件
│   ├── string.js
│   └── string.test.js
├── components/
│   ├── Button.jsx
│   └── Button.test.jsx
└── __tests__/             # 集中放置测试文件
    ├── integration/
    │   └── api.test.js
    └── unit/
        └── utils.test.js

测试文件结构 #

javascript
// user.test.js

// 1. 导入依赖
import { User } from './user';
import { Database } from './database';

// 2. Mock(如果需要)
jest.mock('./database');

// 3. 主要测试套件
describe('User', () => {
  // 4. 共享变量
  let user;
  let mockDb;

  // 5. 生命周期钩子
  beforeEach(() => {
    mockDb = new Database();
    user = new User(mockDb);
  });

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

  // 6. 分组测试
  describe('constructor', () => {
    test('should create user with default values', () => {
      expect(user.id).toBeDefined();
      expect(user.name).toBe('');
    });
  });

  describe('setName', () => {
    test('should update name', () => {
      user.setName('John');
      expect(user.name).toBe('John');
    });

    test('should throw error for empty name', () => {
      expect(() => user.setName('')).toThrow('Name cannot be empty');
    });
  });

  describe('save', () => {
    test('should save to database', async () => {
      await user.save();
      expect(mockDb.save).toHaveBeenCalled();
    });
  });
});

测试模式 #

参数化测试 #

javascript
// 使用 test.each 进行参数化测试
test.each([
  [1, 2, 3],
  [2, 3, 5],
  [5, 5, 10],
])('add(%i, %i) should return %i', (a, b, expected) => {
  expect(add(a, b)).toBe(expected);
});

// 使用对象参数
test.each([
  { a: 1, b: 2, expected: 3 },
  { a: 2, b: 3, expected: 5 },
  { a: -1, b: -2, expected: -3 },
])('add($a, $b) should return $expected', ({ a, b, expected }) => {
  expect(add(a, b)).toBe(expected);
});

// describe.each
describe.each([
  ['positive', 1, 2, 3],
  ['negative', -1, -2, -3],
])('%s numbers', (name, a, b, expected) => {
  test(`add(${a}, ${b}) = ${expected}`, () => {
    expect(add(a, b)).toBe(expected);
  });
});

表格驱动测试 #

javascript
const testCases = [
  { input: 'hello', expected: 'HELLO' },
  { input: 'WORLD', expected: 'WORLD' },
  { input: 'MiXeD', expected: 'MIXED' },
  { input: '', expected: '' },
];

describe('toUpperCase', () => {
  testCases.forEach(({ input, expected }) => {
    test(`"${input}" should become "${expected}"`, () => {
      expect(toUpperCase(input)).toBe(expected);
    });
  });
});

错误处理测试 #

测试抛出错误 #

javascript
function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

test('should throw error when dividing by zero', () => {
  expect(() => divide(1, 0)).toThrow('Division by zero');
  expect(() => divide(1, 0)).toThrow(Error);
  expect(() => divide(1, 0)).toThrow(/division/i);
});

// 测试异步错误
test('should throw error for invalid input', async () => {
  await expect(fetchUser(-1)).rejects.toThrow('Invalid user ID');
});

测试异步代码 #

回调函数 #

javascript
// 使用 done 参数
test('callback test', (done) => {
  function callback(data) {
    expect(data).toBe('result');
    done();
  }

  fetchData(callback);
});

Promise #

javascript
// 返回 Promise
test('promise test', () => {
  return fetchData().then(data => {
    expect(data).toBe('result');
  });
});

// 使用 resolves/rejects
test('resolves test', () => {
  return expect(fetchData()).resolves.toBe('result');
});

test('rejects test', () => {
  return expect(fetchError()).rejects.toThrow('error');
});

async/await #

javascript
test('async test', async () => {
  const data = await fetchData();
  expect(data).toBe('result');
});

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

实用技巧 #

测试描述模板 #

javascript
describe('功能名称', () => {
  describe('方法名称', () => {
    describe('场景描述', () => {
      test('应该 [预期行为]', () => {
        // 测试代码
      });
    });
  });
});

// 示例
describe('ShoppingCart', () => {
  describe('addItem', () => {
    describe('当商品库存充足时', () => {
      test('应该成功添加商品到购物车', () => {
        // ...
      });
    });

    describe('当商品库存不足时', () => {
      test('应该抛出库存不足错误', () => {
        // ...
      });
    });
  });
});

使用注释分隔 #

javascript
describe('UserService', () => {
  // ============================================
  // 创建用户
  // ============================================
  describe('create', () => {
    test('should create user with valid data', () => {});
    test('should throw error for invalid email', () => {});
  });

  // ============================================
  // 更新用户
  // ============================================
  describe('update', () => {
    test('should update user name', () => {});
    test('should throw error for non-existent user', () => {});
  });
});

下一步 #

现在你已经掌握了 Jest 基础测试的编写方法,接下来学习 断言匹配器 了解更多断言方式!

最后更新:2026-03-28