When writing tests with Mocha.js, you need a way to check whether your code produces the expected result.
For example, if a function should return 10, your test needs to verify that the actual result is really 10.
This is where Chai comes in.
Chai is an assertion library for JavaScript that can be used with Mocha and other testing frameworks. It provides readable assertion styles such as expect, assert, and should.
In this guide, you will learn how to use Chai with Mocha.js through practical examples.
We will cover:
- What Chai is
- Why use Chai with Mocha
- Installing Chai
- Using
expect - Using
assert - Using
should - Testing strings, numbers, arrays, and objects
- Testing errors
- Testing asynchronous code
- Common Chai assertions
- Best practices
- Common mistakes
What Is Chai?
Chai is a JavaScript assertion library.
An assertion is simply a statement that checks whether something is true or matches an expected value.
For example:
expect(10).to.equal(10);
This means:
Expect
10to be equal to10.
If the assertion passes, the test continues.
If it fails, Chai throws an error and Mocha marks the test as failed.
Chai can be used with different JavaScript testing frameworks, including Mocha.
Why Use Chai With Mocha?
Mocha is responsible for running and organizing your tests.
For example:
describe("Calculator", function() {
it("should add two numbers", function() {
// test
});
});
But Mocha does not force you to use one particular assertion library. It can work with Node.js’s built-in assert module or libraries such as Chai.
Chai gives you more expressive ways to write assertions.
For example, with Node’s built-in assert:
assert.strictEqual(result, 30);
With Chai:
expect(result).to.equal(30);
The second version can feel more natural to read, especially when tests become more complex.
Installing Chai
First, create a Node.js project if you don’t already have one:
npm init -y
Install Mocha and Chai:
npm install --save-dev mocha chai
You can verify that they were added to your package.json.
Your development dependencies will look similar to:
{
"devDependencies": {
"chai": "...",
"mocha": "..."
}
}
Chai’s current package documentation uses:
npm i chai
for installation.
Mocha and Chai: Different Responsibilities
It is important to understand that Mocha and Chai do different jobs.
Mocha
Mocha provides the testing structure and test runner.
describe();
it();
before();
after();
Chai
Chai provides assertions.
expect();
assert();
should();
Think of it like this:
Mocha
↓
Organizes and runs tests
↓
Chai
↓
Checks whether results are correct
Using Chai’s expect Style
The expect style is one of the most popular ways to use Chai.
You import expect from Chai:
const { expect } = require("chai");
Then use it inside your Mocha tests:
describe("Calculator", function() {
it("should add two numbers", function() {
const result = 10 + 20;
expect(result).to.equal(30);
});
});
The assertion:
expect(result).to.equal(30);
is easy to understand:
Expect the result to equal 30.
Chai’s expect API uses chainable language such as to, be, have, and not to construct readable assertions.
Understanding expect().to
You will frequently see syntax like:
expect(value).to.equal(expectedValue);
For example:
expect(5).to.equal(5);
Here:
expect(5)
means:
This is the value we are testing.
.to
starts the readable assertion chain.
.equal(5)
checks whether the value is strictly equal to 5.
The words such as to, be, and have make the assertion easier to read.
Common Chai Assertions
Let’s look at the assertions you will use most often.
equal()
Use equal() for strict equality.
expect(10).to.equal(10);
Another example:
const name = "John";
expect(name).to.equal("John");
Chai’s equal() uses strict equality (===).
not.equal()
You can check that two values are not equal.
expect(10).to.not.equal(20);
This means:
Expect 10 not to be equal to 20.
Testing Boolean Values
You can check whether a value is exactly true or false.
expect(true).to.be.true;
And:
expect(false).to.be.false;
These assertions check the actual boolean value.
Chai’s documentation recommends using specific assertions such as .true or .false when you actually expect those exact values rather than relying on a generic truthy check.
Testing Truthy Values
You can use:
expect(value).to.be.ok;
For example:
const user = {
name: "John"
};
expect(user).to.be.ok;
However, when you know the exact value you expect, a more specific assertion is generally better.
For example:
expect(user.name).to.equal("John");
rather than only checking:
expect(user.name).to.be.ok;
Chai’s documentation also recommends specific expectations where possible.
Testing Strings
Chai provides several useful string assertions.
const message = "Hello World";
expect(message).to.be.a("string");
You can check the exact value:
expect(message).to.equal("Hello World");
You can also check whether a string includes another string:
expect(message).to.include("World");
And:
expect(message).to.not.include("JavaScript");
Testing Numbers
You can test numbers with:
expect(20).to.be.a("number");
You can check greater or smaller values:
expect(20).to.be.greaterThan(10);
expect(5).to.be.lessThan(10);
You can also check ranges:
expect(50).to.be.within(1, 100);
However, when you know the exact expected value, prefer an exact assertion:
expect(20).to.equal(20);
instead of relying on a range when the range is not what the behavior actually requires. Chai’s documentation makes the same distinction in its assertion guidance.
Testing Arrays
Suppose we have:
const users = ["John", "Mike", "Sarah"];
We can check that the value is an array:
expect(users).to.be.an("array");
Check its length:
expect(users).to.have.lengthOf(3);
Check whether it contains a value:
expect(users).to.include("John");
And:
expect(users).to.not.include("David");
Testing Objects
Suppose we have:
const user = {
id: 1,
name: "John",
age: 25
};
You can check that it is an object:
expect(user).to.be.an("object");
Check whether it has a property:
expect(user).to.have.property("name");
You can also check the property’s value:
expect(user).to.have.property("name", "John");
Chai supports property assertions with optional expected values.
Deep Equality for Objects
Consider these two objects:
const actual = {
name: "John",
age: 25
};
const expected = {
name: "John",
age: 25
};
This will not work as a normal strict equality check:
expect(actual).to.equal(expected);
Why?
Because JavaScript objects are compared by reference.
Instead, use:
expect(actual).to.deep.equal(expected);
Now Chai compares the contents of the objects.
Chai’s deep.equal() is specifically designed for deep equality comparisons of objects and arrays.
Testing Arrays With deep.equal()
The same idea works with arrays:
const actual = [1, 2, 3];
const expected = [1, 2, 3];
expect(actual).to.deep.equal(expected);
This checks that the contents are equal.
Testing null
You can check for null:
expect(null).to.be.null;
For example:
const user = null;
expect(user).to.be.null;
Testing undefined
You can check for undefined:
expect(undefined).to.be.undefined;
For example:
let result;
expect(result).to.be.undefined;
Testing Functions
You can check whether a value is a function:
function add(a, b) {
return a + b;
}
expect(add).to.be.a("function");
This can be useful when testing modules that return functions or callbacks.
Testing Errors With Chai
Testing errors is another useful feature.
Suppose we have:
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
We can test that the function throws:
expect(() => divide(10, 0)).to.throw();
We can check the specific error type:
expect(() => divide(10, 0))
.to.throw(Error);
And we can check the message:
expect(() => divide(10, 0))
.to.throw("Cannot divide by zero");
You can also combine the checks:
expect(() => divide(10, 0))
.to.throw(Error, "Cannot divide by zero");
Chai recommends asserting the expected error type and message rather than simply asserting that some unknown error occurs.
Important: Pass a Function to throw()
A common mistake is writing:
expect(divide(10, 0)).to.throw();
This is incorrect.
The function executes before Chai gets a chance to check it.
Instead, wrap the function call:
expect(() => divide(10, 0)).to.throw();
Now Chai can execute the function and verify that it throws.
Using Chai With an Actual Mocha Test
Let’s create a small calculator.
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("Cannot divide by zero");
}
return a / b;
}
module.exports = {
add,
subtract,
multiply,
divide
};
Now create a test file.
calculator.test.js
const { expect } = require("chai");
const {
add,
subtract,
multiply,
divide
} = require("./calculator");
describe("Calculator", function() {
describe("add()", function() {
it("should add two numbers", function() {
const result = add(10, 20);
expect(result).to.equal(30);
});
});
describe("subtract()", function() {
it("should subtract two numbers", function() {
const result = subtract(20, 10);
expect(result).to.equal(10);
});
});
describe("multiply()", function() {
it("should multiply two numbers", function() {
const result = multiply(10, 5);
expect(result).to.equal(50);
});
});
describe("divide()", function() {
it("should divide two numbers", function() {
const result = divide(20, 5);
expect(result).to.equal(4);
});
it("should throw an error when dividing by zero", function() {
expect(() => divide(20, 0))
.to.throw("Cannot divide by zero");
});
});
});
Run the tests:
npx mocha
You should see the tests passing.
Chai’s Three Assertion Styles
Chai provides three main assertion styles:
expectshouldassert
The expect and should interfaces are BDD-style, while assert is the TDD-style interface.
Let’s look at each one.
1. Expect Style
We have already used this style.
const { expect } = require("chai");
expect(result).to.equal(10);
Another example:
expect(user)
.to.have.property("name")
.that.equals("John");
The expect style is popular because the assertions read almost like English.
2. Assert Style
Chai also provides an assert interface.
const { assert } = require("chai");
Then:
assert.equal(result, 10);
You can check strict equality:
assert.strictEqual(result, 10);
You can check deep equality:
assert.deepEqual(actual, expected);
You can check whether a value is an array:
assert.isArray(users);
And whether a value is an object:
assert.isObject(user);
The Assert API is Chai’s TDD-style interface.
3. Should Style
The third style is should.
const { should } = require("chai");
should();
Then you can write:
const name = "John";
name.should.be.a("string");
name.should.equal("John");
For arrays:
const users = ["John", "Mike"];
users.should.be.an("array");
users.should.have.lengthOf(2);
The should style adds a should property to objects through Object.prototype. Because of this behavior, it has some limitations, and Chai’s documentation recommends understanding those differences before using it.
For most beginner projects, expect is a comfortable starting point.
Expect vs Assert vs Should
Here is the same idea using all three styles.
Expect
expect(result).to.equal(10);
Assert
assert.strictEqual(result, 10);
Should
result.should.equal(10);
All three can perform assertions.
The choice mostly comes down to readability and project preference.
Testing Asynchronous Code With Chai
Chai can also be used when testing asynchronous code with Mocha.
Suppose we have:
function getUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1,
name: "John"
});
}, 500);
});
}
We can test it using async/await:
it("should return user data", async function() {
const user = await getUser();
expect(user).to.be.an("object");
expect(user).to.have.property("id", 1);
expect(user).to.have.property("name", "John");
});
Mocha handles the asynchronous test, while Chai performs the assertions.
This is a good example of how the two tools work together:
Mocha
↓
Runs the async test
↓
await getUser()
↓
Chai
↓
Checks the result
Testing an Async Error
Suppose the function rejects with an error:
function getUser() {
return Promise.reject(
new Error("Unable to fetch user")
);
}
You can test it with:
it("should return an error", async function() {
try {
await getUser();
} catch (error) {
expect(error).to.be.instanceOf(Error);
expect(error.message)
.to.equal("Unable to fetch user");
}
});
The important part is that Mocha waits for the asynchronous test, while Chai checks the error.
Testing API Response Objects
Chai is particularly useful when testing objects returned from APIs.
Imagine an API returns:
const response = {
status: 200,
data: {
id: 1,
name: "John",
role: "admin"
}
};
You can write:
expect(response).to.be.an("object");
expect(response).to.have.property("status", 200);
expect(response.data)
.to.have.property("id", 1);
expect(response.data)
.to.have.property("name", "John");
expect(response.data)
.to.have.property("role", "admin");
This makes it easy to verify specific parts of an API response.
Useful Chai Assertions Cheat Sheet
Here are some commonly used assertions.
| Assertion | Purpose |
|---|---|
expect(value).to.equal(x) | Strict equality |
expect(value).to.not.equal(x) | Not equal |
expect(value).to.be.true | Exactly true |
expect(value).to.be.false | Exactly false |
expect(value).to.be.null | Checks null |
expect(value).to.be.undefined | Checks undefined |
expect(value).to.be.a("string") | Checks type |
expect(value).to.be.an("array") | Checks array |
expect(value).to.be.an("object") | Checks object |
expect(array).to.include(value) | Checks array/string inclusion |
expect(array).to.have.lengthOf(3) | Checks length |
expect(obj).to.have.property("name") | Checks property |
expect(obj).to.deep.equal(expected) | Deep equality |
expect(fn).to.throw() | Checks thrown error |
expect(value).to.be.greaterThan(5) | Greater than |
expect(value).to.be.lessThan(10) | Less than |
expect(value).to.be.within(1, 10) | Range check |
Using .not
Chai allows you to negate assertions using .not.
For example:
expect(10).to.not.equal(20);
For arrays:
expect([1, 2, 3])
.to.not.include(5);
For objects:
expect(user)
.to.not.have.property("password");
However, don’t overuse .not.
A precise positive assertion is usually easier to understand.
For example:
expect(user).to.have.property("name", "John");
is generally clearer than trying to express many possible things that the user should not be.
Chai’s own documentation recommends focusing on the exact expected result where possible.
Best Practices When Using Chai With Mocha
1. Choose One Assertion Style
If your project uses:
expect()
try to use that style consistently.
Avoid randomly mixing:
expect()
assert()
should
unless there is a specific reason.
2. Prefer Specific Assertions
Instead of:
expect(result).to.be.ok;
when you know the expected value, use:
expect(result).to.equal(100);
Specific assertions make test failures easier to understand.
3. Test Behavior, Not Implementation
Suppose your function returns:
{
name: "John",
age: 25
}
Focus on what the caller needs:
expect(user.name).to.equal("John");
Don’t create tests that depend heavily on internal implementation details.
4. Test Error Cases
Don’t only test:
Correct input → Correct result
Also test:
Invalid input → Expected error
For example:
expect(() => divide(10, 0))
.to.throw("Cannot divide by zero");
5. Keep Assertions Readable
Compare:
expect(result).to.equal(100);
with complicated assertions that combine many unrelated conditions.
Simple assertions make it easier to understand what failed.
Common Mistakes
Mistake 1: Comparing Objects With equal()
Incorrect:
expect(actual).to.equal(expected);
For separate objects, use:
expect(actual).to.deep.equal(expected);
Mistake 2: Calling a Function Before throw()
Incorrect:
expect(divide(10, 0)).to.throw();
Correct:
expect(() => divide(10, 0)).to.throw();
Mistake 3: Checking Only Truthiness
Instead of:
expect(result).to.be.ok;
when you know what the result should be:
expect(result).to.equal(50);
Mistake 4: Forgetting to Wait for Async Code
Incorrect:
it("should return user", function() {
getUser().then((user) => {
expect(user.name).to.equal("John");
});
});
Use async/await:
it("should return user", async function() {
const user = await getUser();
expect(user.name).to.equal("John");
});
A Simple Project Structure
A small Node.js project using Mocha and Chai could look like this:
my-project/
│
├── src/
│ └── calculator.js
│
├── test/
│ └── calculator.test.js
│
├── package.json
└── package-lock.json
Your test file can then contain:
const { expect } = require("chai");
const calculator = require("../src/calculator");
describe("Calculator", function() {
it("should add numbers", function() {
const result = calculator.add(10, 20);
expect(result).to.equal(30);
});
});
Run it with:
npx mocha
Mocha + Chai in a Real Testing Workflow
When you use both tools together, the workflow is straightforward:
Write application code
↓
Write Mocha test
↓
Run the test
↓
Chai checks the result
↓
Test passes or fails
For example:
it("should calculate total price", function() {
const total = calculateTotal(100, 3);
expect(total).to.equal(300);
});
Mocha runs the test.
Chai checks:
total === 300
If the result is 300, the test passes.
If the result is something else, Chai throws an assertion error and Mocha reports the test as failed.
Frequently Asked Questions
Is Chai required for Mocha?
No.
Mocha allows you to use different assertion libraries, including Node.js’s built-in assert module and Chai.
What is the difference between Mocha and Chai?
Mocha is a test framework and test runner.
Chai is an assertion library.
Mocha runs the test, while Chai checks whether the result matches what you expect.
Which Chai style should beginners use?
The expect style is a good starting point because its assertions are easy to read:
expect(result).to.equal(10);
Can Chai test asynchronous code?
Yes. Chai can perform assertions on values returned by asynchronous operations. Mocha handles the asynchronous test flow using callbacks, Promises, or async/await.
Can I use Chai without Mocha?
Yes.
Chai is an assertion library and can be paired with other JavaScript testing frameworks as well.
Should I use Chai’s assert or expect?
Both are valid.
For beginners, expect is often easier to read:
expect(result).to.equal(10);
But the best choice is usually the style your project and team use consistently.
Conclusion
Mocha gives you the structure and test runner you need to organize and execute JavaScript tests.
Chai gives you expressive assertions for checking whether your code behaves as expected.
Together, they provide a simple testing workflow:
Mocha → Run and organize tests
Chai → Make assertions
You can use Chai to test:
- Numbers
- Strings
- Arrays
- Objects
- Functions
- Errors
- API responses
- Asynchronous results
The expect style is a great place to start:
const { expect } = require("chai");
expect(result).to.equal(expected);
Once you understand common assertions such as equal(), deep.equal(), include(), property(), and throw(), writing readable Mocha tests becomes much easier.




