Mocha 教程
1. 核心知识点讲解
1.1 Mocha 简介
Mocha 是一个功能强大的 JavaScript 测试框架,专注于异步测试。它提供了灵活的测试结构和丰富的钩子函数,支持多种断言库和测试风格。
1.2 安装和配置
安装 Mocha 和断言库:
npm install --save-dev mocha chai配置 package.json:
{
"scripts": {
"test": "mocha"
}
}创建 mocha.opts 配置文件(Mocha v6 及以下):
--recursive
--reporter spec
--timeout 5000使用命令行参数(Mocha v7+):
mocha --recursive --reporter spec --timeout 50001.3 基本测试结构
测试文件命名约定:
- 以
.test.js或.spec.js结尾的文件 - 放在
test目录下
基本测试结构:
const assert = require('assert');
describe('测试套件', function() {
it('测试用例', function() {
assert.equal(1 + 1, 2);
});
});
// 使用箭头函数
describe('测试套件', () => {
it('测试用例', () => {
assert.equal(1 + 1, 2);
});
});1.4 测试生命周期
测试钩子:
describe('测试套件', function() {
before(function() {
// 在所有测试开始前运行
});
after(function() {
// 在所有测试结束后运行
});
beforeEach(function() {
// 在每个测试开始前运行
});
afterEach(function() {
// 在每个测试结束后运行
});
it('测试用例 1', function() {
// 测试代码
});
it('测试用例 2', function() {
// 测试代码
});
});1.5 异步测试
回调风格:
describe('异步测试', function() {
it('使用回调', function(done) {
setTimeout(function() {
assert.equal(1 + 1, 2);
done(); // 必须调用 done() 表示测试完成
}, 1000);
});
});Promise 风格:
describe('异步测试', function() {
it('使用 Promise', function() {
return new Promise(function(resolve) {
setTimeout(function() {
assert.equal(1 + 1, 2);
resolve();
}, 1000);
});
});
});async/await 风格:
describe('异步测试', function() {
it('使用 async/await', async function() {
await new Promise(resolve => setTimeout(resolve, 1000));
assert.equal(1 + 1, 2);
});
});1.6 断言库
内置断言:
const assert = require('assert');
assert.equal(1, 1);
assert.strictEqual(1, '1'); // 失败
assert.deepEqual({ a: 1 }, { a: 1 });
assert.throws(() => { throw new Error('错误'); });Chai 断言库:
const { expect, assert, should } = require('chai');
// expect 风格
expect(1 + 1).to.equal(2);
expect({ a: 1 }).to.deep.equal({ a: 1 });
expect(true).to.be.true;
// assert 风格
assert.equal(1 + 1, 2);
assert.deepEqual({ a: 1 }, { a: 1 });
// should 风格
should();
(1 + 1).should.equal(2);
({ a: 1 }).should.deep.equal({ a: 1 });1.7 测试覆盖率
安装 istanbul/nyc:
npm install --save-dev nyc配置 package.json:
{
"scripts": {
"test": "mocha",
"coverage": "nyc mocha"
}
}运行测试并生成覆盖率报告:
npm run coverage1.8 测试报告
内置报告器:
spec- 默认,详细的测试结果dot- 点矩阵形式nyan- 彩虹猫形式json- JSON 格式html- HTML 格式
使用自定义报告器:
mocha --reporter nyan2. 实用案例分析
2.1 基本测试案例
场景: 测试一个简单的计算器函数
实现:
// src/calculator.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('除数不能为零');
}
return a / b;
}
module.exports = {
add,
subtract,
multiply,
divide
};
// test/calculator.test.js
const { expect } = require('chai');
const { add, subtract, multiply, divide } = require('../src/calculator');
describe('计算器测试', function() {
describe('加法测试', function() {
it('1 + 1 应该等于 2', function() {
expect(add(1, 2)).to.equal(3);
});
it('-1 + 1 应该等于 0', function() {
expect(add(-1, 1)).to.equal(0);
});
it('0 + 0 应该等于 0', function() {
expect(add(0, 0)).to.equal(0);
});
});
describe('减法测试', function() {
it('5 - 3 应该等于 2', function() {
expect(subtract(5, 3)).to.equal(2);
});
it('1 - 5 应该等于 -4', function() {
expect(subtract(1, 5)).to.equal(-4);
});
});
describe('乘法测试', function() {
it('2 * 3 应该等于 6', function() {
expect(multiply(2, 3)).to.equal(6);
});
it('-1 * 5 应该等于 -5', function() {
expect(multiply(-1, 5)).to.equal(-5);
});
});
describe('除法测试', function() {
it('6 / 2 应该等于 3', function() {
expect(divide(6, 2)).to.equal(3);
});
it('5 / 2 应该等于 2.5', function() {
expect(divide(5, 2)).to.equal(2.5);
});
it('除数为零应该抛出错误', function() {
expect(() => divide(1, 0)).to.throw('除数不能为零');
});
});
});2.2 异步测试案例
场景: 测试一个异步函数
实现:
// src/userService.js
function getUserById(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: `User ${id}` });
} else {
reject(new Error('无效的用户 ID'));
}
}, 1000);
});
}
module.exports = { getUserById };
// test/userService.test.js
const { expect } = require('chai');
const { getUserById } = require('../src/userService');
describe('用户服务测试', function() {
this.timeout(2000); // 增加超时时间
it('获取有效用户 ID 应该返回用户信息', async function() {
const user = await getUserById(1);
expect(user).to.deep.equal({ id: 1, name: 'User 1' });
});
it('获取无效用户 ID 应该抛出错误', async function() {
try {
await getUserById(-1);
// 如果没有抛出错误,测试失败
expect.fail('应该抛出错误');
} catch (error) {
expect(error.message).to.equal('无效的用户 ID');
}
});
it('使用 Promise 测试异步函数', function() {
return getUserById(2)
.then(user => {
expect(user).to.deep.equal({ id: 2, name: 'User 2' });
});
});
it('使用回调测试异步函数', function(done) {
getUserById(3)
.then(user => {
expect(user).to.deep.equal({ id: 3, name: 'User 3' });
done();
})
.catch(done);
});
});2.3 测试钩子案例
场景: 测试一个需要初始化和清理的组件
实现:
// src/database.js
class Database {
constructor() {
this.connection = null;
}
connect() {
this.connection = { id: Math.random() };
return this.connection;
}
disconnect() {
this.connection = null;
}
query(sql) {
if (!this.connection) {
throw new Error('未连接数据库');
}
return `执行 SQL: ${sql}`;
}
}
module.exports = Database;
// test/database.test.js
const { expect } = require('chai');
const Database = require('../src/database');
describe('数据库测试', function() {
let db;
beforeEach(function() {
// 每个测试前创建新的数据库实例
db = new Database();
});
afterEach(function() {
// 每个测试后清理
if (db.connection) {
db.disconnect();
}
});
it('连接数据库应该返回连接对象', function() {
const connection = db.connect();
expect(connection).to.be.an('object');
expect(db.connection).to.equal(connection);
});
it('未连接数据库执行查询应该抛出错误', function() {
expect(() => db.query('SELECT * FROM users')).to.throw('未连接数据库');
});
it('连接数据库后执行查询应该成功', function() {
db.connect();
const result = db.query('SELECT * FROM users');
expect(result).to.equal('执行 SQL: SELECT * FROM users');
});
it('断开连接后连接应该为 null', function() {
db.connect();
db.disconnect();
expect(db.connection).to.be.null;
});
});3. 代码示例
3.1 完整的测试项目结构
项目结构:
project/
├── src/
│ ├── calculator.js
│ ├── userService.js
│ └── database.js
├── test/
│ ├── calculator.test.js
│ ├── userService.test.js
│ └── database.test.js
├── package.json
└── .nycrc3.2 运行测试
运行所有测试:
npm test运行特定测试文件:
mocha test/calculator.test.js运行测试并生成覆盖率报告:
npm run coverage3.3 测试配置示例
package.json:
{
"name": "mocha-test-demo",
"version": "1.0.0",
"description": "Mocha 测试示例",
"main": "index.js",
"scripts": {
"test": "mocha --recursive --reporter spec --timeout 5000",
"coverage": "nyc mocha --recursive --reporter spec --timeout 5000"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.1.3",
"nyc": "^15.1.0"
}
}.nycrc:
{
"exclude": [
"test/**",
"node_modules/**"
],
"reporter": [
"text-summary",
"html"
],
"check-coverage": true,
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}3.4 高级测试技巧
测试重试:
describe('重试测试', function() {
it('应该重试失败的测试', function() {
// 随机失败的测试
if (Math.random() > 0.5) {
throw new Error('随机失败');
}
}).retries(3); // 最多重试 3 次
});测试跳过:
describe('跳过测试', function() {
it.skip('应该跳过这个测试', function() {
// 这个测试不会运行
});
it('应该运行这个测试', function() {
// 这个测试会运行
});
});条件测试:
describe('条件测试', function() {
(process.env.NODE_ENV === 'test' ? it : it.skip)('只在测试环境运行', function() {
// 这个测试只在测试环境运行
});
});测试速度标记:
describe('测试速度', function() {
it('快速测试', function() {
// 快速运行的测试
});
it('慢速测试', function() {
// 慢速运行的测试
}).slow(1000); // 标记为慢速测试
});4. 总结
Mocha 是一个功能强大的 JavaScript 测试框架,提供了灵活的测试结构和丰富的钩子函数。本教程介绍了 Mocha 的核心概念,包括:
- 安装和配置
- 基本测试结构
- 测试生命周期和钩子函数
- 异步测试支持
- 断言库集成
- 测试覆盖率报告
- 测试报告器
通过学习这些概念,你可以开始在项目中使用 Mocha 编写测试,提高代码质量和可靠性。Mocha 的灵活性使其适合各种类型的测试场景,从简单的单元测试到复杂的集成测试。
5. 进一步学习资源
希望本教程对你学习 Mocha 有所帮助!