Chai Assertions
Three Assertion Styles
const chai = require('chai');
// 1. assert style (TDD)
const { assert } = chai;
assert.equal(3, 3);
assert.strictEqual('hello', 'hello');
assert.deepEqual({ a: 1 }, { a: 1 });
assert.isNull(null);
assert.isArray([]);
assert.include([1, 2, 3], 2);
assert.throws(() => { throw new Error('oops') }, Error, 'oops');
// 2. expect style (BDD) โ most popular
const { expect } = chai;
expect(42).to.equal(42);
expect('hello').to.be.a('string');
expect([1, 2, 3]).to.have.lengthOf(3);
expect(null).to.be.null;
expect(undefined).to.be.undefined;
expect(true).to.be.true;
// 3. should style (BDD, extends Object.prototype)
chai.should();
(42).should.equal(42);
'hello'.should.be.a('string');
[1, 2, 3].should.have.lengthOf(3);
// Note: doesn't work on null/undefined values directly
expect โ Chaining & Common Assertions
const { expect } = require('chai');
// Type checks
expect(42).to.be.a('number');
expect('hello').to.be.a('string');
expect([]).to.be.an('array');
expect({}).to.be.an('object');
expect(() => {}).to.be.a('function');
// Equality
expect('foo').to.equal('foo'); // strict ===
expect([1, 2]).to.deep.equal([1, 2]); // deep equality
expect({ a: 1 }).to.eql({ a: 1 }); // alias for deep.equal
// Object properties
expect({ a: 1, b: 2 }).to.have.property('a', 1);
expect({ a: 1, b: 2 }).to.include({ a: 1 });
expect(obj).to.have.all.keys('a', 'b');
expect(obj).to.have.any.keys('a', 'c');
// Arrays / strings
expect([1, 2, 3]).to.include(2);
expect('hello world').to.include('world');
expect([1, 2, 3]).to.have.members([3, 2, 1]); // order-independent
expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
// Ranges
expect(7).to.be.above(5);
expect(3).to.be.below(5);
expect(5).to.be.within(1, 10);
// Regex
expect('hello123').to.match(/\d+/);
// Negation
expect('foo').to.not.equal('bar');
expect([]).to.not.include(1);
Deep Equality & Nested Objects
// Deep equal โ nested objects and arrays
expect({ a: { b: { c: 3 } } }).to.deep.equal({ a: { b: { c: 3 } } });
// Nested property access
expect({ a: { b: 1 } }).to.have.nested.property('a.b', 1);
expect({ arr: [1, 2, 3] }).to.have.nested.property('arr[1]', 2);
// Deep include โ partial match
expect({ a: 1, b: 2, c: 3 }).to.deep.include({ a: 1, b: 2 });
expect([{ a: 1 }, { b: 2 }]).to.deep.include({ a: 1 });
// Own properties only
expect({ a: 1 }).to.have.own.property('a');
// Chai-subset plugin for partial matching
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
expect({ a: 1, b: 2, c: 3 }).to.containSubset({ a: 1, b: 2 });
Exception Assertions
// Synchronous throws
expect(() => JSON.parse('invalid')).to.throw();
expect(() => JSON.parse('invalid')).to.throw(SyntaxError);
expect(() => throw new Error('boom')).to.throw('boom');
expect(() => throw new Error('Not found')).to.throw(/not found/i);
// Not throw
expect(() => JSON.parse('{"valid": true}')).to.not.throw();
// Async throws โ with chai-as-promised
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
await expect(Promise.reject(new Error('async error')))
.to.be.rejectedWith('async error');
await expect(asyncFunction())
.to.eventually.have.property('id');
await expect(asyncFunction())
.to.be.fulfilled;
Plugins
| Plugin | Install | Purpose |
|---|---|---|
| chai-as-promised | npm i chai-as-promised | Async/Promise assertions |
| sinon-chai | npm i sinon-chai | Sinon spy/stub assertions |
| chai-http | npm i chai-http | HTTP request testing |
| chai-subset | npm i chai-subset | Partial object matching |
| chai-datetime | npm i chai-datetime | Date comparison |
// sinon-chai usage
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const spy = sinon.spy();
spy('arg1');
expect(spy).to.have.been.calledWith('arg1');
expect(spy).to.have.been.calledOnce;
Custom Messages & Custom Assertions
// Custom failure message (last argument to most assertions)
expect(user.age, 'user age should be a number').to.be.a('number');
assert.isAbove(response.time, 0, 'response time must be positive');
// Add custom assertion method
chai.Assertion.addMethod('validEmail', function () {
const email = this._obj;
new chai.Assertion(email).to.be.a('string');
this.assert(
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
`expected #{this} to be a valid email`,
`expected #{this} not to be a valid email`
);
});
// Usage:
expect('[email protected]').to.be.a.validEmail();
expect('invalid-email').to.not.be.a.validEmail();