Chai.js Cheatsheet - expect, should & assert Assertions

This reference is for JavaScript developers writing tests with Mocha, Jest integrations, or plain Node. Chai gives three interchangeable styles (expect, should, assert); this cheatsheet shows the same check written each way. It covers equality, deep comparison of objects/arrays, type and inclusion checks, thrown-error assertions, numeric bounds, and plugin-backed helpers such as eventually (chai-as-promised) and HTTP status (chai-http). After reading you should be able to write readable assertions for values, collections, thrown errors, and promises, and know when deep.equal beats equal.

Languages·44 commands·Last updated 2026-07-21
chaiTestingAssertionsjavascript

Typical Use Case

For front-end and Node.js test authors who describe business rules in BDD style. All three assertion styles appear here so you can read whichever one a given repository uses: expect is the most universal and reads closest to natural language, should is the shortest to write but has sharp edges around null values, and assert is the TDD flavour with more explicit function calls. The sheet covers how to choose between equality and deep equality, where .equal is strict while .deep.equal compares layer by layer, plus type and containment assertions, the four ways of asserting on exceptions (by type, by message pattern, and asserting that nothing is thrown), and the Promise and HTTP assertions that only work once a plugin is registered. The troubleshooting entries explain why a question mark renders as ??, the difference between .own.property and .property, and why should crashes on null.

Expect Style (BDD) 8

expect(value).to.equal(expected)
Equality assertion (strict ===)
expect(value).to.be.a("string")
Type assertion
expect(array).to.include(item)
Inclusion assertion
expect(value).to.be.true
Boolean assertion
expect(value).to.not.be.null
Non-null assertion
expect(array).to.have.lengthOf(3)
Length assertion
expect(value).to.exist
Existence assertion (non-null and non-undefined)
expect(obj).to.have.property("key", "value")
Property and its value assertion

Should Style (BDD) 5

value.should.equal(expected)
Equality assertion
value.should.be.a("string")
Type assertion
array.should.include(item)
Inclusion assertion
value.should.be.true
Boolean assertion
value.should.exist
Existence assertion

Assert Style (TDD) 7

assert.equal(actual, expected)
Equality assertion
assert.notEqual(actual, expected)
Inequality assertion
assert.deepEqual(actual, expected)
Deep equality
assert.isTrue(value)
Truthy assertion
assert.isArray(value)
Array assertion
assert.throws(fn)
Throws exception assertion
assert.fail(actual, expected, "message")
Explicit failure assertion

Equality & Deep Comparison 6

expect(value).to.deep.equal(obj)
Deep-compare objects/arrays
expect(value).to.eql(obj)
Alias of deep.equal
expect(value).to.not.equal(other)
Inequality assertion
expect(obj).to.have.own.property("key")
Own property assertion (no prototype chain)
expect(obj).to.have.nested.property("a.b.c")
Nested property assertion
expect(arr).to.deep.equal([1, 2, 3])
Array deep comparison

Type & Inclusion 6

expect(value).to.be.an("array")
Array type assertion
expect(value).to.be.an.instanceof(Date)
Instance type assertion
expect(str).to.match(/^foo/)
Regex match assertion
expect(str).to.have.string("bar")
String contains substring
expect(obj).to.include({ key: "value" })
Object partial inclusion
expect(arr).to.have.members([1, 2, 3])
Array members exactly equal

Exceptions & Throws 6

expect(fn).to.throw(Error)
Throw a specific type of exception
expect(fn).to.throw("error message")
Throw with a specific message
expect(fn).to.throw(/message/)
Regex-match the exception message
expect(fn).to.not.throw()
Do not throw
assert.throws(fn, TypeError)
TDD throw assertion
assert.doesNotThrow(fn)
TDD no-throw assertion

Numbers, Booleans & Plugins 6

expect(num).to.be.above(5)
Greater-than assertion
expect(num).to.be.at.least(5)
At-least (>=) assertion
expect(num).to.be.below(10)
Less-than assertion
expect(num).to.be.within(1, 10)
Within-range (inclusive) assertion
expect(promise).to.eventually.equal("resolved")
Promise assertion (needs chai-as-promised)
expect(res).to.have.status(200)
HTTP status assertion (needs chai-http)

Parameter matrix

ParameterEffectExample
expect(value).to.equal(x)Strict equality assertion (===)expect(value).to.equal(expected)
expect(value).to.be.a("string")Type assertionexpect(value).to.be.a("string")
expect(arr).to.include(item)Assertion that a collection includes an elementexpect(array).to.include(item)
expect(value).to.be.trueBoolean truth assertionexpect(value).to.be.true
expect(value).to.not.be.nullNon-null assertionexpect(value).to.not.be.null
expect(arr).to.have.lengthOf(3)Length assertionexpect(array).to.have.lengthOf(3)
expect(value).to.existExistence assertion (neither null nor undefined)expect(value).to.exist
expect(obj).to.have.property("k", "v")Assert that a property exists and its value matchesexpect(obj).to.have.property("key", "value")
value.should.equal(x)should-style equality assertionvalue.should.equal(expected)
value.should.existshould-style existence assertionvalue.should.exist
assert.equal(a, b)Loose equality comparison (==)assert.equal(actual, expected)
assert.notEqual(a, b)Loose inequality assertionassert.notEqual(actual, expected)
assert.deepEqual(a, b)Recursively compare every level of an object/arrayassert.deepEqual(actual, expected)
assert.isTrue(v) / assert.isArray(v)Boolean and array type assertionsassert.isArray(value)
assert.throws(fn)Assert that calling it throwsassert.throws(fn)
assert.fail(a, b, "message")Fail the test unconditionally, for unreachable branchesassert.fail(actual, expected, "message")
.to.deep.equal(obj)Deep-compare objects/arraysexpect(value).to.deep.equal(obj)
.to.eql(obj)Alias of .deep.equalexpect(value).to.eql(obj)
.to.not.equal(other)Inequality assertionexpect(value).to.not.equal(other)
.to.have.own.property("k")Own-property assertion, excluding the prototype chainexpect(obj).to.have.own.property("key")
.to.have.nested.property("a.b.c")Nested-path property assertionexpect(obj).to.have.nested.property("a.b.c")
.to.be.an("array")Array type assertionexpect(value).to.be.an("array")
.to.be.an.instanceof(Date)Instance type assertionexpect(value).to.be.an.instanceof(Date)
.to.match(/^foo/)Regex match assertionexpect(str).to.match(/^foo/)
.to.have.string("bar")String substring assertionexpect(str).to.have.string("bar")
.to.include({ key: "value" })Partial object inclusion assertionexpect(obj).to.include({ key: "value" })
.to.have.members([1, 2, 3])Assert that array members match exactlyexpect(arr).to.have.members([1, 2, 3])
.to.throw(Error)Assert it throws an exception of the given typeexpect(fn).to.throw(Error)
.to.throw("error message")Assert the error message contains the substringexpect(fn).to.throw("error message")
.to.throw(/message/)Match the error message with a regexexpect(fn).to.throw(/message/)
.to.not.throw()Assert it does not throwexpect(fn).to.not.throw()
assert.throws(fn, TypeError)TDD-style throw assertionassert.throws(fn, TypeError)
assert.doesNotThrow(fn)TDD-style no-throw assertionassert.doesNotThrow(fn)
.to.be.above(5) / .to.be.below(10)Greater-than / less-than assertionexpect(num).to.be.above(5)
.to.be.at.least(5)Greater-than-or-equal assertionexpect(num).to.be.at.least(5)
.to.be.within(1, 10)Range assertion, bounds inclusiveexpect(num).to.be.within(1, 10)
.to.eventually.equal("resolved")Promise assertion, requires the chai-as-promised pluginexpect(promise).to.eventually.equal("resolved")
.to.have.status(200)HTTP status assertion, requires the chai-http pluginexpect(res).to.have.status(200)

Common pitfalls

SymptomUsing should-style assertions on a possibly-null value crashes the test while reading .should, aborting the suite.

CauseThe should style requires reading a property off the object, so null/undefined throws before the assertion even runs.

FixSwitch to the expect style: expect(value).to.not.be.null, where the assertion function safely accepts empty values first.

SymptomThe fields of the object clearly match, yet .to.equal fails.

Cause.equal uses strict equality and compares addresses for reference types; two objects with identical content but different references will not pass.

FixUse .to.deep.equal or .eql to compare content layer by layer.

SymptomAn array assertion fails even though it "looks like the same array."

CauseStrict equality compares references; the array rebuilt in the test is not the same reference as the one in the object under test.

FixCompare content with expect(arr).to.deep.equal([1, 2, 3]), or compare only the member set with .have.members.

SymptomYou want to assert "this property exists on the object," but inherited prototype properties also pass.

Cause.property walks the prototype chain, whereas checking an own property should use .own.property.

FixUse expect(obj).to.have.own.property("key") to consider only the own properties of the object.

SymptomThe error message shows expected as ??, so you cannot tell what is being compared.

CauseThe expression passed as the expected value itself evaluated to undefined, so Chai can only render undefined as a placeholder.

FixCompute the expected value before passing it in, or add a custom message to the assertion to ensure a definite value is compared.

SymptomA .to.throw() assertion passes even though the function actually threw a different error.

CauseWith no arguments it only checks whether something was thrown, not the type or the message.

FixBe specific: .to.throw(Error) checks the type, .to.throw("message") or .to.throw(/message/) checks the message content.

SymptomAsserting the resolved value of a Promise reports eventually is not a function.

CausePromise-related assertions depend on the chai-as-promised plugin, which is not registered.

FixRegister chai-as-promised at the test entry point before using chained forms like expect(promise).to.eventually.

Troubleshooting

  1. 1Confirm the actual type of the value before choosing an assertion

    expect(value).to.be.a("string")

    When a type assertion fails it prints the actual type, faster than guessing the assertion syntax.

  2. 2Distinguish shallow from deep comparison

    expect(value).to.deep.equal(obj)

    If the content matches but .to.equal fails, that signals you should switch to a deep comparison.

  3. 3Compare array members without caring about order

    expect(arr).to.have.members([1, 2, 3])

    It passes when the member sets match, even if the order differs.

  4. 4Verify the error branch actually throws the given exception

    expect(fn).to.throw(TypeError)

    Wrapping the function under test in the assertion fails immediately with the actual error if nothing is thrown or the type differs.

  5. 5Confirm a property is an own property, not inherited from the prototype

    expect(obj).to.have.own.property("key")

    If .property passes but .own.property fails, the property comes from the prototype chain.

  6. 6Assert the HTTP response status

    expect(res).to.have.status(200)

    Register the chai-http plugin first; on failure it prints the actual status code.

Command Examples

One BDD chain covering type, content, and null

const res = await parse(body); expect(res).to.be.an("object"); expect(res.data).to.have.lengthOf(3); expect(res.error).to.not.exist; expect(res.data[0].name).to.equal("tom");

Chai BDD assertions are chainable: expect appears once, and .to / .be / .have are interchangeable, so the failure message reads like a path to the offending property. One chain beats four separate expects when you want to see where it broke.

Deep comparison of objects and arrays

expect(actual).to.deep.equal({ id: 1, tags: ["a", "b"] }); // eql is an alias of deep.equal, in a shorter style expect(rows).to.eql([{ id: 1 }, { id: 2 }]); // Array members must match exactly, order-independent expect(ids).to.have.members([3, 1, 2]);

equal is strict ===, so two structurally identical objects with different references always fail — use deep.equal / eql for structure. members demands an exact set (any order), which is what you want when asserting "exactly these rows, nothing extra".

Covering the error branch: three ways to match a throw

expect(() => JSON.parse("{")).to.throw(); expect(() => parse("x")).to.throw(TypeError); expect(() => parse("x")).to.throw(/invalid/); // Also assert the branch that should not throw, or it passes silently expect(() => parse("{}")).to.not.throw();

A bare to.throw() only proves that something threw, so a bug with the wrong error type or message still passes. Narrow it to a type plus a message regex, and add to.not.throw() on the happy path to cover both directions.

Asserting only the fields you care about

expect(user).to.include({ role: "admin" }); expect(user).to.have.own.property("id"); expect(user).to.have.nested.property("profile.address.city");

include is a partial match, so newly added fields do not break the test. own.property looks at own properties only — an inherited one still counts as present and misleads you. nested.property walks a dotted path and pinpoints the missing level.

Numeric ranges plus async assertions

expect(score).to.be.within(0, 100); expect(save()).to.eventually.equal("ok"); // Requires registering chai-as-promised / chai-http first // expect(res).to.have.status(200);

within includes both endpoints, which saves you from hand-writing above(0).and.below(100) and dropping a boundary. eventually and status need plugins such as chai-as-promised and chai-http — without registering them you just get an unknown assertion method.

Common Pitfalls

  • expect(value).to.equal(other) is strict ===, so two structurally identical objects with different references always fail. Forgetting deep.equal produces a "they look the same but it fails" phantom bug.
  • Once a property in a BDD chain is undefined, everything after .to.have.property explodes into unrelated errors. Bisect from the head of the chain instead of editing several assertions at once.
  • assert.equal uses loose ==, so "1" and 1 count as equal. On fields that mix strings and numbers it hides type errors; prefer a strict assertion.
  • expect(fn).to.throw() only proves that something threw. A wrong business error type still passes, so error branches must constrain both the type and the message.
  • Promise assertions need chai-as-promised registered first, otherwise eventually / rejected do not exist and the failure reads "assertion is not a function" — which looks like a broken test environment rather than a missing plugin.
  • With only expect(fn).to.throw() and no positive-path assertion that the call does not throw, a broken error branch (not throwing when it should) is never detected at all.

Tips

  • expect and should are BDD style; assert is TDD style.
  • should requires calling .should on an object and may error on null/undefined.
  • Use .deep.equal for deep comparison; .equal for shallow comparison.
  • chai-as-promised adds Promise assertions; chai-http adds HTTP request assertions.
  • Chain methods like to, be, have only improve readability and do nothing functionally.

FAQ

When should I use should versus expect in chai?

expect(value).to.equal(expected) passes the subject as the first argument and puts the assertion after it; the should style hangs the assertion off the subject, as in value.should.be.a("string"). Two practical differences: expect reads fine with strict null checking in editors, while should works by adding a property to the object — calling .should on null or undefined throws immediately, so nullable values must be asserted with expect, as in expect(value).to.not.be.null. assert is the TDD flavour, written assert.equal(actual, expected), and looks like classic unit-testing frameworks. Pick one per project and stay consistent.

What is the real difference between equal and deep.equal?

expect(value).to.equal(expected) is strict equality (===) on references or primitives, so expect([1,2]).to.equal([1,2]) fails — those are two distinct array objects. Only expect(value).to.deep.equal(obj), short form .to.eql, walks the structure recursively and matches arrays and nested objects. Related: expect(obj).to.have.own.property("key") checks own properties without walking the prototype chain, and expect(obj).to.have.nested.property("a.b.c") asserts along a path. When an assertion fails on values that look identical, check for a missing deep first — that is by far the most common cause.

How do I assert that a function throws when testing an error branch?

The expect style offers four granularities: expect(fn).to.throw(Error) checks only the exception type, expect(fn).to.throw("error message") requires an exact message, expect(fn).to.throw(/message/) matches part of it with a regex, and expect(fn).to.not.throw() asserts the happy path. The critical detail is passing the function itself, not its result — expect(fn()).to.throw() evaluates fn first, so the exception escapes before the assertion runs. The assert equivalents are assert.throws(fn, TypeError) and assert.doesNotThrow(fn). To mark a branch that should be unreachable, use assert.fail(actual, expected, "message") so the test fails loudly rather than passing silently.

How do I assert on Promises with chai?

Async assertions are not in chai core — they come from plugins. chai-as-promised is the common one, adding the eventually modifier so expect(promise).to.eventually.equal("resolved") awaits settlement before comparing, with the rejected form used to assert the failure reason, e.g. expect(promise).to.be.rejectedWith(Error). HTTP responses are chai-http: expect(res).to.have.status(200) validates both the status code and body. If your project already runs on Jest, its built-in resolves and rejects matchers cover these cases and the plugins are unnecessary.

How do include and members differ when asserting on an array?

expect(arr).to.include(item) only requires that the element be present — extra elements do not affect the result, which suits "the item the user is looking for is in there". expect(arr).to.have.members([1, 2, 3]) demands exact correspondence: same length and same values, suited to snapshot-style whole-collection checks. expect(obj).to.include({ key: "value" }) is the object counterpart, comparing only the named keys rather than the whole object. For strings use expect(str).to.have.string("bar") for substring matching, and for numbers the above / below / at.least / within(1, 10) family. Picking the wrong one yields assertions that are too loose or too strict.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us