Mocha 基础测试 #
安装与配置 #
安装 Mocha #
bash
# 全局安装
npm install --global mocha
# 项目本地安装(推荐)
npm install --save-dev mocha
项目初始化 #
bash
# 创建项目
mkdir my-project
cd my-project
npm init -y
# 安装 Mocha 和断言库
npm install --save-dev mocha chai
# 创建测试目录
mkdir test
项目结构 #
text
my-project/
├── src/
│ └── math.js
├── test/
│ └── math.test.js
├── package.json
└── node_modules/
配置 package.json #
json
{
"name": "my-project",
"version": "1.0.0",
"scripts": {
"test": "mocha"
},
"devDependencies": {
"chai": "^4.3.0",
"mocha": "^10.0.0"
}
}
测试的基本结构 #
describe 和 it #
Mocha 使用 describe 和 it 组织测试:
javascript
// test/math.test.js
const { expect } = require('chai');
const { add, subtract } = require('../src/math');
describe('Math', function() {
describe('add', function() {
it('should add two positive numbers', function() {
expect(add(1, 2)).to.equal(3);
});
it('should add negative numbers', function() {
expect(add(-1, -2)).to.equal(-3);
});
it('should add zero', function() {
expect(add(5, 0)).to.equal(5);
});
});
describe('subtract', function() {
it('should subtract two numbers', function() {
expect(subtract(5, 3)).to.equal(2);
});
});
});
第一个测试 #
创建被测试的模块:
javascript
// src/math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
module.exports = { add, subtract, multiply, divide };
创建测试文件:
javascript
// test/math.test.js
const { expect } = require('chai');
const math = require('../src/math');
describe('Math Module', function() {
describe('add', function() {
it('should return sum of two numbers', function() {
expect(math.add(1, 2)).to.equal(3);
});
it('should handle negative numbers', function() {
expect(math.add(-1, -2)).to.equal(-3);
});
it('should handle zero', function() {
expect(math.add(5, 0)).to.equal(5);
});
});
describe('divide', function() {
it('should divide two numbers', function() {
expect(math.divide(6, 2)).to.equal(3);
});
it('should throw error when dividing by zero', function() {
expect(() => math.divide(1, 0)).to.throw('Division by zero');
});
});
});
运行测试:
bash
npm test
# 输出
# Math Module
# add
# ✓ should return sum of two numbers
# ✓ should handle negative numbers
# ✓ should handle zero
# divide
# ✓ should divide two numbers
# ✓ should throw error when dividing by zero
#
# 5 passing (10ms)
测试接口风格 #
Mocha 支持多种测试接口风格:
BDD 风格(推荐) #
javascript
// BDD (Behavior Driven Development)
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
it('should return the correct index when found', function() {
assert.equal([1, 2, 3].indexOf(2), 1);
});
});
});
// 使用 context(describe 的别名)
context('when the array is empty', function() {
it('should return -1', function() {
assert.equal([].indexOf(1), -1);
});
});
TDD 风格 #
javascript
// TDD (Test Driven Development)
suite('Array', function() {
suite('#indexOf()', function() {
test('should return -1 when not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});
配置 TDD 风格:
bash
mocha --ui tdd test/
Exports 风格 #
javascript
// exports 风格
module.exports = {
'Array': {
'#indexOf()': {
'should return -1 when not present': function() {
assert.equal([1, 2, 3].indexOf(4), -1);
}
}
}
};
QUnit 风格 #
javascript
// QUnit 风格
suite('Array');
test('should return -1 when not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
Require 风格 #
javascript
// require 风格
var suite = require('mocha').suite;
var test = require('mocha').test;
suite('Array', function() {
test('should return -1 when not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
组织测试 #
嵌套 describe #
javascript
describe('ShoppingCart', function() {
describe('addItem', function() {
describe('when item is available', function() {
it('should add item to cart', function() {
// ...
});
it('should update total price', function() {
// ...
});
});
describe('when item is out of stock', function() {
it('should throw error', function() {
// ...
});
});
});
describe('removeItem', function() {
it('should remove item from cart', function() {
// ...
});
});
});
使用 context #
context 是 describe 的别名,用于描述测试上下文:
javascript
describe('User Authentication', function() {
context('when credentials are valid', function() {
it('should return access token', function() {
// ...
});
it('should set session cookie', function() {
// ...
});
});
context('when credentials are invalid', function() {
it('should throw authentication error', function() {
// ...
});
it('should not set session cookie', function() {
// ...
});
});
});
测试命名规范 #
好的命名 #
javascript
// ✅ 描述行为
it('should return the sum of two numbers', function() {});
it('should throw error when input is invalid', function() {});
it('should return empty array when no items found', function() {});
// ✅ 使用中文描述
it('应该返回两个数的和', function() {});
it('输入无效时应该抛出错误', function() {});
// ✅ 描述预期结果
it('returns true when user is logged in', function() {});
it('returns false when password is incorrect', function() {});
不好的命名 #
javascript
// ❌ 太模糊
it('test1', function() {});
it('works', function() {});
// ❌ 技术细节而非行为
it('calls function', function() {});
it('returns value', function() {});
// ❌ 太长
it('should return the correct sum when adding two positive numbers together', function() {});
跳过测试 #
skip 方法 #
javascript
// 跳过单个测试
it.skip('this test is skipped', function() {
expect(true).to.equal(false);
});
// 跳过整个 describe
describe.skip('Feature Not Ready', function() {
it('should work', function() {
// 这个测试不会运行
});
});
// 使用 xit 和 xdescribe
xit('this is also skipped', function() {});
xdescribe('this suite is skipped', function() {});
条件跳过 #
javascript
const shouldRunSlowTests = process.env.RUN_SLOW_TESTS === 'true';
(shouldRunSlowTests ? it : it.skip)('slow test', function() {
this.timeout(5000);
// 耗时测试
});
// 根据环境跳过
const isCI = process.env.CI === 'true';
if (!isCI) {
it('local only test', function() {
// 只在本地运行
});
}
仅运行特定测试 #
only 方法 #
javascript
// 只运行这一个测试
it.only('run this test only', function() {
expect(1 + 1).to.equal(2);
});
// 只运行这个 describe
describe.only('Calculator', function() {
it('should add', function() {
// 这个会运行
});
});
// 其他测试都会被跳过
it('this test is skipped', function() {
// 不会运行
});
使用 fit 和 fdescribe #
javascript
// fit = it.only
fit('focused test', function() {});
// fdescribe = describe.only
fdescribe('focused suite', function() {});
测试超时 #
设置超时时间 #
javascript
// 设置整个套件超时
describe('slow operations', function() {
this.timeout(5000); // 5秒
it('should complete within 5 seconds', function(done) {
setTimeout(done, 4000);
});
});
// 设置单个测试超时
it('slow test', function(done) {
this.timeout(3000);
setTimeout(done, 2500);
});
// 使用 arrow function 注意事项
// ❌ 不能使用 arrow function 设置 this.timeout
describe('wrong', () => {
it('test', () => {
this.timeout(1000); // 错误!this 不是 Mocha 上下文
});
});
// ✅ 使用普通函数
describe('correct', function() {
it('test', function() {
this.timeout(1000); // 正确
});
});
不同级别的超时设置 #
javascript
// 全局超时(命令行)
// mocha --timeout 5000 test/
// 套件级别
describe('suite', function() {
this.timeout(3000);
// 测试级别
it('test', function() {
this.timeout(1000);
});
});
// 钩子级别
describe('suite', function() {
beforeEach(function() {
this.timeout(2000);
});
});
测试慢速警告 #
javascript
// 设置慢速阈值
describe('operations', function() {
this.slow(100); // 超过 100ms 标记为慢
it('fast test', function() {
// < 50ms: 正常
});
it('medium test', function() {
// 50-100ms: 黄色警告
});
it('slow test', function() {
// > 100ms: 红色警告
});
});
测试重试 #
对于不稳定的测试,可以设置重试次数:
javascript
// 整个套件重试
describe('flaky tests', function() {
this.retries(3);
it('unstable test', function() {
// 最多重试 3 次
});
});
// 单个测试重试
it('flaky test', function() {
this.retries(2);
// 最多重试 2 次
});
测试文件组织 #
文件命名约定 #
text
test/
├── unit/ # 单元测试
│ ├── math.test.js
│ └── string.test.js
├── integration/ # 集成测试
│ ├── api.test.js
│ └── database.test.js
├── e2e/ # 端到端测试
│ └── user-flow.test.js
└── helpers/ # 测试辅助工具
└── setup.js
测试文件结构 #
javascript
// test/user.test.js
// 1. 导入依赖
const { expect } = require('chai');
const User = require('../src/user');
const Database = require('../src/database');
// 2. 测试套件
describe('User', function() {
// 3. 共享变量
let user;
let db;
// 4. 生命周期钩子
before(async function() {
db = new Database('test');
await db.connect();
});
after(async function() {
await db.disconnect();
});
beforeEach(function() {
user = new User('John', 'john@example.com');
});
// 5. 分组测试
describe('constructor', function() {
it('should create user with name and email', function() {
expect(user.name).to.equal('John');
expect(user.email).to.equal('john@example.com');
});
});
describe('save', function() {
it('should save user to database', async function() {
const saved = await user.save(db);
expect(saved.id).to.exist;
});
});
});
运行测试 #
命令行运行 #
bash
# 运行所有测试
mocha
# 运行指定文件
mocha test/math.test.js
# 运行指定目录
mocha test/unit/**/*.js
# 使用通配符
mocha "test/**/*.test.js"
# 递归运行
mocha --recursive test/
常用选项 #
bash
# 指定报告器
mocha --reporter spec
# 监听文件变化
mocha --watch
# 并行运行
mocha --parallel
# 指定超时时间
mocha --timeout 5000
# 显示慢测试
mocha --slow 100
# 增加详细输出
mocha --verbose
# 禁用颜色输出
mocha --no-colors
使用 npm scripts #
json
{
"scripts": {
"test": "mocha",
"test:watch": "mocha --watch",
"test:coverage": "nyc mocha",
"test:unit": "mocha test/unit",
"test:integration": "mocha test/integration"
}
}
实用技巧 #
AAA 模式 #
Arrange(准备)- Act(执行)- Assert(断言):
javascript
it('should add item to cart', function() {
// Arrange - 准备测试数据
const cart = new Cart();
const item = { id: 1, name: 'Product', price: 100 };
// Act - 执行被测试的操作
cart.addItem(item);
// Assert - 验证结果
expect(cart.items).to.have.lengthOf(1);
expect(cart.items[0]).to.deep.equal(item);
expect(cart.total).to.equal(100);
});
参数化测试 #
javascript
// 使用 forEach 实现参数化
const testCases = [
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 3, expected: 5 },
{ a: -1, b: -2, expected: -3 },
{ a: 0, b: 0, expected: 0 }
];
describe('add', function() {
testCases.forEach(({ a, b, expected }) => {
it(`should return ${expected} for ${a} + ${b}`, function() {
expect(add(a, b)).to.equal(expected);
});
});
});
测试描述模板 #
javascript
describe('功能名称', function() {
describe('方法名称', function() {
context('场景描述', function() {
it('应该 [预期行为]', function() {
// 测试代码
});
});
});
});
// 示例
describe('ShoppingCart', function() {
describe('addItem', function() {
context('当商品库存充足时', function() {
it('应该成功添加商品到购物车', function() {
// ...
});
});
context('当商品库存不足时', function() {
it('应该抛出库存不足错误', function() {
// ...
});
});
});
});
下一步 #
现在你已经掌握了 Mocha 基础测试的编写方法,接下来学习 断言匹配器 了解更多断言方式!
最后更新:2026-03-28