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