Mocha 断言库 #
断言库概述 #
Mocha 本身不包含断言功能,需要配合断言库使用。常用的断言库有:
| 断言库 | 特点 | 推荐场景 |
|---|---|---|
| Node.js assert | 内置,无需安装 | 简单项目 |
| Chai | 功能丰富,多种风格 | 企业项目 |
| Should.js | 链式调用,语义化 | BDD 风格 |
| Expect | 简洁易用 | 通用场景 |
Node.js 内置 assert #
基本使用 #
javascript
const assert = require('assert');
describe('Node.js Assert', function() {
it('assert.equal', function() {
assert.equal(1 + 1, 2);
assert.equal('hello', 'hello');
});
it('assert.strictEqual', function() {
assert.strictEqual(1, 1);
assert.strictEqual('hello', 'hello');
});
it('assert.deepEqual', function() {
assert.deepEqual({ a: 1 }, { a: 1 });
assert.deepEqual([1, 2, 3], [1, 2, 3]);
});
});
常用断言方法 #
javascript
const assert = require('assert');
describe('Assert Methods', function() {
// 相等性
it('equal / notEqual', function() {
assert.equal(1, 1);
assert.notEqual(1, 2);
});
it('strictEqual / notStrictEqual', function() {
assert.strictEqual(1, 1);
assert.notStrictEqual(1, '1');
});
it('deepEqual / notDeepEqual', function() {
assert.deepEqual({ a: 1 }, { a: 1 });
assert.notDeepEqual({ a: 1 }, { a: 2 });
});
it('deepStrictEqual', function() {
assert.deepStrictEqual({ a: 1 }, { a: 1 });
});
// 布尔值
it('ok / ifError', function() {
assert.ok(true);
assert.ok(1);
assert.ifError(null);
assert.ifError(undefined);
});
// 抛出错误
it('throws / doesNotThrow', function() {
assert.throws(function() {
throw new Error('test error');
}, Error);
assert.throws(function() {
throw new Error('test error');
}, /test error/);
assert.doesNotThrow(function() {
return 1;
});
});
// 异步错误
it('rejects / doesNotReject', async function() {
await assert.rejects(
Promise.reject(new Error('async error')),
Error
);
await assert.doesNotReject(
Promise.resolve('ok')
);
});
// 包含
it('match / doesNotMatch', function() {
assert.match('hello world', /world/);
assert.doesNotMatch('hello', /world/);
});
});
自定义错误消息 #
javascript
const assert = require('assert');
it('with custom message', function() {
const actual = 1;
const expected = 2;
assert.equal(actual, expected, `Expected ${expected} but got ${actual}`);
});
Chai 断言库 #
安装 Chai #
bash
npm install --save-dev chai
三种断言风格 #
Chai 提供三种断言风格:
javascript
// expect 风格(推荐)
const expect = require('chai').expect;
expect(1 + 1).to.equal(2);
// should 风格
const should = require('chai').should();
(1 + 1).should.equal(2);
// assert 风格
const assert = require('chai').assert;
assert.equal(1 + 1, 2);
Chai Expect 风格 #
基本相等 #
javascript
const { expect } = require('chai');
describe('Expect - Equality', function() {
it('equal / eql', function() {
// 严格相等(===)
expect(1).to.equal(1);
expect('hello').to.equal('hello');
// 深度相等
expect({ a: 1 }).to.deep.equal({ a: 1 });
expect([1, 2, 3]).to.deep.equal([1, 2, 3]);
// eql 是 deep.equal 的简写
expect({ a: 1 }).to.eql({ a: 1 });
});
it('not', function() {
expect(1).to.not.equal(2);
expect({ a: 1 }).to.not.equal({ a: 1 });
expect([1, 2]).to.not.eql([2, 1]);
});
});
布尔值断言 #
javascript
const { expect } = require('chai');
describe('Expect - Boolean', function() {
it('true / false', function() {
expect(true).to.be.true;
expect(false).to.be.false;
expect(1 === 1).to.be.true;
});
it('ok / notOk', function() {
expect(true).to.be.ok;
expect(1).to.be.ok;
expect('hello').to.be.ok;
expect([]).to.be.ok;
expect(false).to.not.be.ok;
expect(0).to.not.be.ok;
expect('').to.not.be.ok;
expect(null).to.not.be.ok;
});
});
类型检查 #
javascript
const { expect } = require('chai');
describe('Expect - Type', function() {
it('type checking', function() {
expect('hello').to.be.a('string');
expect(123).to.be.a('number');
expect(true).to.be.a('boolean');
expect([]).to.be.an('array');
expect({}).to.be.an('object');
expect(function() {}).to.be.a('function');
expect(null).to.be.a('null');
expect(undefined).to.be.an('undefined');
expect(new Date()).to.be.a('date');
expect(/regex/).to.be.a('regexp');
expect(new Error()).to.be.an('error');
});
it('instanceof', function() {
class Person {}
const person = new Person();
expect(person).to.be.an.instanceof(Person);
expect([]).to.be.an.instanceof(Array);
expect({}).to.be.an.instanceof(Object);
});
});
数值比较 #
javascript
const { expect } = require('chai');
describe('Expect - Numbers', function() {
it('comparison', function() {
expect(5).to.be.above(3);
expect(5).to.be.gt(3); // greater than
expect(5).to.be.greaterThan(3);
expect(3).to.be.below(5);
expect(3).to.be.lt(5); // less than
expect(3).to.be.lessThan(5);
expect(5).to.be.at.least(5);
expect(5).to.be.gte(5); // greater than or equal
expect(3).to.be.at.most(3);
expect(3).to.be.lte(3); // less than or equal
});
it('within', function() {
expect(5).to.be.within(1, 10);
expect(50).to.be.within(40, 60);
});
it('closeTo', function() {
// 浮点数近似相等
expect(1.5).to.be.closeTo(1.4, 0.2);
expect(3.14159).to.be.closeTo(3.14, 0.01);
});
});
字符串断言 #
javascript
const { expect } = require('chai');
describe('Expect - Strings', function() {
it('include / contain', function() {
expect('hello world').to.include('world');
expect('hello world').to.contain('hello');
expect('hello world').to.include.oneOf(['hello', 'hi']);
});
it('startWith / endWith', function() {
expect('hello world').to.startWith('hello');
expect('hello world').to.endWith('world');
});
it('match', function() {
expect('hello').to.match(/^hello$/);
expect('test@example.com').to.match(/@/);
});
it('length', function() {
expect('hello').to.have.lengthOf(5);
expect('hello').to.have.length.above(3);
expect('hello').to.have.length.within(3, 10);
});
});
数组断言 #
javascript
const { expect } = require('chai');
describe('Expect - Arrays', function() {
it('include / contain', function() {
expect([1, 2, 3]).to.include(2);
expect([1, 2, 3]).to.include.deep.members([1, 2]);
expect([{ a: 1 }, { b: 2 }]).to.include.deep.members([{ a: 1 }]);
});
it('members', function() {
expect([1, 2, 3]).to.have.members([3, 2, 1]);
expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
expect([1, 2, 3]).to.include.ordered.members([1, 2]);
});
it('length', function() {
expect([1, 2, 3]).to.have.lengthOf(3);
expect([1, 2, 3]).to.have.length.above(2);
expect([1, 2, 3]).to.have.length.below(5);
});
it('empty', function() {
expect([]).to.be.empty;
expect([1, 2]).to.not.be.empty;
});
it('nested include', function() {
const arr = [{ a: { b: 1 } }];
expect(arr).to.have.nested.deep.include({ 'a.b': 1 });
});
});
对象断言 #
javascript
const { expect } = require('chai');
describe('Expect - Objects', function() {
it('property', function() {
const obj = { a: 1, b: 2 };
expect(obj).to.have.property('a');
expect(obj).to.have.property('a', 1);
expect(obj).to.have.property('b').that.is.a('number');
});
it('nested property', function() {
const obj = { a: { b: { c: 1 } } };
expect(obj).to.have.nested.property('a.b.c', 1);
expect(obj).to.have.nested.property('a.b');
});
it('keys', function() {
const obj = { a: 1, b: 2, c: 3 };
expect(obj).to.have.all.keys('a', 'b', 'c');
expect(obj).to.have.all.keys(['a', 'b', 'c']);
expect(obj).to.have.any.keys('a', 'd');
expect(obj).to.contain.all.keys('a', 'b');
});
it('own property', function() {
function Person(name) {
this.name = name;
}
Person.prototype.age = 30;
const person = new Person('John');
expect(person).to.have.own.property('name');
expect(person).to.not.have.own.property('age');
});
it('empty', function() {
expect({}).to.be.empty;
expect({ a: 1 }).to.not.be.empty;
});
});
函数断言 #
javascript
const { expect } = require('chai');
describe('Expect - Functions', function() {
it('throw', function() {
function throwError() {
throw new Error('test error');
}
expect(throwError).to.throw();
expect(throwError).to.throw(Error);
expect(throwError).to.throw('test error');
expect(throwError).to.throw(/test/);
});
it('throw with arguments', function() {
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
expect(() => divide(1, 0)).to.throw('Division by zero');
expect(() => divide(1, 1)).to.not.throw();
});
it('respondTo', function() {
const obj = {
method: function() {},
property: 'value'
};
expect(obj).to.respondTo('method');
expect(obj).to.not.respondTo('property');
});
it('itself', function() {
class Person {
static create() {}
greet() {}
}
expect(Person).itself.to.respondTo('create');
expect(Person).to.not.itself.respondTo('greet');
});
});
存在性检查 #
javascript
const { expect } = require('chai');
describe('Expect - Existence', function() {
it('exist', function() {
expect(1).to.exist;
expect('hello').to.exist;
expect({}).to.exist;
expect([]).to.exist;
expect(null).to.not.exist;
expect(undefined).to.not.exist;
});
it('undefined / null', function() {
expect(undefined).to.be.undefined;
expect(null).to.be.null;
expect(1).to.not.be.undefined;
expect(1).to.not.be.null;
});
it('NaN', function() {
expect(NaN).to.be.NaN;
expect(1).to.not.be.NaN;
});
it('finite', function() {
expect(1).to.be.finite;
expect(Infinity).to.not.be.finite;
});
});
链式调用 #
Chai 支持语义化的链式调用:
javascript
const { expect } = require('chai');
describe('Chaining', function() {
it('chains', function() {
// 这些词可以任意组合,不影响断言结果
expect(1).to.equal(1);
expect(1).to.be.equal(1);
expect(1).to.be.a('number').and.to.equal(1);
// 可用的链式词
// to, be, been, is, that, which, and, has, have, with, at, of, same, but, does, still
});
it('complex chains', function() {
const obj = { a: 1, b: 2 };
expect(obj)
.to.be.an('object')
.that.has.property('a')
.that.is.a('number')
.and.equals(1);
});
});
Chai Should 风格 #
基本使用 #
javascript
const should = require('chai').should();
describe('Should Style', function() {
it('basic assertions', function() {
(1 + 1).should.equal(2);
'hello'.should.be.a('string');
[1, 2, 3].should.have.lengthOf(3);
({ a: 1 }).should.have.property('a');
});
it('should exist', function() {
const obj = { a: 1 };
obj.should.exist;
obj.should.be.an('object');
});
it('should throw', function() {
(function() {
throw new Error('test');
}).should.throw(Error);
});
});
注意事项 #
javascript
// ❌ 不能对 null 或 undefined 使用 should
// null.should.be.null; // TypeError
// ✅ 使用 expect 或先检查存在性
const { expect } = require('chai');
expect(null).to.be.null;
Chai Assert 风格 #
基本使用 #
javascript
const assert = require('chai').assert;
describe('Chai Assert Style', function() {
it('equality', function() {
assert.equal(1, 1);
assert.strictEqual(1, 1);
assert.deepEqual({ a: 1 }, { a: 1 });
assert.notEqual(1, 2);
});
it('type', function() {
assert.typeOf('hello', 'string');
assert.typeOf(123, 'number');
assert.typeOf([], 'array');
assert.instanceOf(new Date(), Date);
});
it('boolean', function() {
assert.isTrue(true);
assert.isFalse(false);
assertisOk(1);
assert.isNotOk(0);
});
it('comparison', function() {
assert.isAbove(5, 3);
assert.isBelow(3, 5);
assert.isAtLeast(5, 5);
assert.isAtMost(3, 3);
});
it('inclusion', function() {
assert.include([1, 2, 3], 2);
assert.include('hello world', 'world');
assert.include({ a: 1, b: 2 }, { a: 1 });
});
it('throws', function() {
assert.throws(function() {
throw new Error('test');
}, Error);
});
it('object', function() {
assert.property({ a: 1 }, 'a');
assert.propertyVal({ a: 1 }, 'a', 1);
assert.hasAllKeys({ a: 1, b: 2 }, ['a', 'b']);
});
});
Chai 插件 #
chai-as-promised #
用于测试 Promise:
bash
npm install --save-dev chai-as-promised
javascript
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const { expect } = chai;
describe('Promise Assertions', function() {
it('should resolve', function() {
return expect(Promise.resolve(1)).to.eventually.equal(1);
});
it('should reject', function() {
return expect(Promise.reject(new Error('fail')))
.to.be.rejectedWith('fail');
});
it('should be fulfilled', function() {
return expect(Promise.resolve('ok')).to.be.fulfilled;
});
it('async/await', async function() {
await expect(Promise.resolve(1)).to.eventually.equal(1);
await expect(Promise.reject('error')).to.be.rejected;
});
});
chai-http #
用于测试 HTTP 请求:
bash
npm install --save-dev chai-http
javascript
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('./app');
chai.use(chaiHttp);
const { expect } = chai;
describe('HTTP Requests', function() {
it('GET /api/users', function(done) {
chai.request(app)
.get('/api/users')
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body).to.be.an('array');
done();
});
});
it('POST /api/users', function(done) {
chai.request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' })
.end(function(err, res) {
expect(res).to.have.status(201);
expect(res.body).to.have.property('id');
done();
});
});
});
最佳实践 #
选择合适的断言风格 #
javascript
// 推荐:使用 expect 风格
const { expect } = require('chai');
// 清晰易读
expect(user.name).to.equal('John');
expect(user).to.have.property('email');
expect(response).to.have.status(200);
断言组织 #
javascript
describe('User', function() {
let user;
beforeEach(function() {
user = {
name: 'John',
email: 'john@example.com',
age: 30,
roles: ['admin', 'user']
};
});
describe('properties', function() {
it('should have correct name', function() {
expect(user).to.have.property('name', 'John');
});
it('should have valid email', function() {
expect(user.email).to.match(/@/);
});
it('should have age above 18', function() {
expect(user.age).to.be.above(18);
});
});
describe('roles', function() {
it('should include admin role', function() {
expect(user.roles).to.include('admin');
});
it('should have at least one role', function() {
expect(user.roles).to.have.length.above(0);
});
});
});
自定义断言消息 #
javascript
const { expect } = require('chai');
it('with custom message', function() {
const value = 5;
expect(value, `Expected value to be 10 but got ${value}`).to.equal(10);
});
下一步 #
现在你已经掌握了断言库的使用方法,接下来学习 生命周期钩子 了解测试的前置和后置处理!
最后更新:2026-03-28