jest, mocha, jasmine, and chai form the core ecosystem for JavaScript testing, but they serve different roles. jest is a complete testing platform that includes a test runner, assertion library, and mocking tools out of the box. mocha is a flexible test runner that requires external libraries for assertions and mocking. chai is a dedicated assertion library often paired with mocha. jasmine is an older all-in-one framework similar to jest but with a different philosophy on configuration and mocking. Understanding their architectural differences is critical for building maintainable test suites.
Building a reliable test suite requires more than just writing assertions. You need a runner to execute tests, a library to check values, and tools to isolate code. jest, mocha, jasmine, and chai approach this problem differently. jest and jasmine are complete frameworks, while mocha and chai are modular tools that you combine. Let's look at how they handle the core challenges of testing.
The biggest decision is whether you want a single package or a custom stack.
jest bundles everything. You install one package and get a runner, assertions, and mocks. This reduces decision fatigue.
// jest: Everything in one import context
// No need to import expect separately in modern setups
test('adds numbers', () => {
expect(1 + 1).toBe(2);
});
jasmine is also all-in-one but relies more on global functions. It was the default for Angular for many years.
// jasmine: Global functions describe and it
describe('Calculator', () => {
it('adds numbers', () => {
expect(1 + 1).toBe(2);
});
});
mocha is just the runner. You must install an assertion library like chai to check values.
// mocha + chai: Explicit imports required
import { expect } from 'chai';
describe('Calculator', () => {
it('adds numbers', () => {
expect(1 + 1).to.equal(2);
});
});
chai is not a runner. It cannot execute tests on its own. It is strictly for assertions.
// chai: Used inside a runner like mocha
import { assert } from 'chai';
// assert style example
assert.equal(1 + 1, 2);
How you write checks impacts how easy tests are to read during code reviews.
jest uses a concise, chainable syntax. It is built to be readable without extra words.
// jest: Concise matchers
expect(user.isActive).toBe(true);
expect(items).toHaveLength(5);
expect(response).toHaveProperty('id');
jasmine looks very similar to jest because jest originally forked its assertion library.
// jasmine: Similar to jest
expect(user.isActive).toBe(true);
expect(items.length).toEqual(5);
expect(response).toBeDefined();
chai offers three different styles: assert, expect, and should. This lets teams pick what feels natural.
// chai: Expect style (most common)
expect(user.isActive).to.be.true;
// chai: Should style (modifies Object prototype)
user.should.have.property('isActive').that.is.true;
// chai: Assert style (traditional)
assert.equal(user.isActive, true);
mocha has no built-in assertions. It relies entirely on what you pair it with, usually chai.
// mocha: Delegates to chai
// The syntax depends on the imported assertion library
expect(value).to.not.be.null;
Mocking external calls (like APIs or databases) is essential for unit tests.
jest has a powerful built-in mocking system. You can mock entire modules with one line.
// jest: Auto-mocking modules
jest.mock('../api');
test('fetches data', async () => {
api.getUser.mockResolvedValue({ name: 'Alice' });
const user = await api.getUser();
expect(user.name).toBe('Alice');
});
jasmine uses "spies" to track function calls. It is built-in but less flexible than jest for module mocking.
// jasmine: Spies on methods
const api = { getUser: () => {} };
spyOn(api, 'getUser').and.returnValue({ name: 'Alice' });
expect(api.getUser()).toEqual({ name: 'Alice' });
expect(api.getUser).toHaveBeenCalled();
mocha does not include mocking. You typically install sinon or use chai plugins.
// mocha + sinon: External mock library
import sinon from 'sinon';
const stub = sinon.stub(api, 'getUser').resolves({ name: 'Alice' });
// Test logic here
stub.restore();
chai does not handle mocking. It only checks values. You must combine it with sinon for this.
// chai + sinon: Verification only
import { expect } from 'chai';
import sinon from 'sinon';
const stub = sinon.stub(api, 'getUser');
// ... execute code ...
expect(stub.calledOnce).to.be.true;
Setup time varies significantly between these tools.
jest aims for zero configuration. It finds test files automatically and works with Babel/TypeScript out of the box.
// jest: package.json
{
"scripts": {
"test": "jest"
}
}
// No config file needed for basic usage
jasmine requires an init step to create a config file. It is more manual than jest.
// jasmine: CLI init required
// npx jasmine init
// Creates spec/support/jasmine.json
{
"spec_dir": "spec",
"spec_files": ["**/*[sS]pec.js"]
}
mocha needs a config file or command line flags to know where tests live. It is very explicit.
// mocha: .mocharc.json
{
"spec": "test/**/*.test.js",
"require": "@babel/register"
}
chai has no configuration for running tests. You only configure plugins if you add them.
// chai: Plugin setup
import chai from 'chai';
import chaiHttp from 'chai-http';
chai.use(chaiHttp);
// No runner config here
Snapshot testing saves the output of a component and compares it later to detect changes.
jest made snapshot testing popular. It is built-in and very easy to use.
// jest: Snapshot testing
test('renders correctly', () => {
const tree = renderer.create(<Button />).toJSON();
expect(tree).toMatchSnapshot();
});
jasmine does not have built-in snapshot testing. You need external plugins like jasmine-snapshot.
// jasmine: Requires plugin
import { addMatchers } from 'jasmine-snapshot';
addMatchers();
expect(component).toMatchSnapshot();
mocha does not support snapshots natively. You must use chai-jest-snapshot or similar.
// mocha + chai-jest-snapshot
import chaiJestSnapshot from 'chai-jest-snapshot';
chai.use(chaiJestSnapshot);
expect(component).to.matchSnapshot();
chai has no snapshot features. It relies on extensions to add this capability.
// chai: Extension required
// No native support
chai.use(require('chai-jest-snapshot'));
Modern JavaScript relies heavily on async code. The tools handle this differently.
jest handles promises automatically. You just return the promise or use async/await.
// jest: Async/Await support
test('fetches data', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
jasmine also supports async/await natively in recent versions.
// jasmine: Async/Await support
it('fetches data', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
mocha supports async/await but requires you to ensure the test function returns the promise or uses done().
// mocha: Async/Await support
it('fetches data', async () => {
const data = await fetchData();
expect(data).to.exist;
});
chai does not run tests. It asserts values within the async flow managed by the runner.
// chai: Assertion inside async
// Runner (mocha) handles the flow
expect(data).to.have.property('id');
Despite their differences, these tools share common goals and patterns.
All four use the describe and it (or test) pattern to organize suites.
// Common structure across all
describe('User Module', () => {
it('should create a user', () => {
// Test logic
});
});
They all provide setup and teardown hooks for preparing test environments.
// jest/mocha/jasmine
beforeEach(() => { resetDatabase(); });
afterEach(() => { cleanup(); });
All packages provide or support TypeScript definitions for type-safe testing.
// All support TS
const value: number = 5;
expect(value).toBe(5);
| Feature | jest | mocha | jasmine | chai |
|---|---|---|---|---|
| Type | Full Framework | Test Runner | Full Framework | Assertion Library |
| Assertions | Built-in | External Needed | Built-in | Built-in (Core Purpose) |
| Mocking | Built-in | External Needed | Built-in (Spies) | External Needed |
| Snapshots | Built-in | Plugin Needed | Plugin Needed | Plugin Needed |
| Config | Zero-Config | Manual | Manual | N/A (Library) |
| Speed | Fast (Parallel) | Fast | Moderate | N/A |
jest is the default choice for modern web development. It removes the need to glue tools together and offers the best developer experience with features like watch mode and snapshots. It is the safest bet for React, Vue, and Node projects.
mocha + chai is the power-user stack. Use this if you need specific control over your test environment or are working in a browser environment where jest might be too heavy. It allows you to swap out parts of the testing stack as needed.
jasmine is a legacy contender. While still maintained, it has lost ground to jest. Only choose this if you are maintaining an older Angular application or have a specific requirement that jest cannot meet.
Final Thought: For 90% of new projects, jest provides the best balance of power and simplicity. Use mocha and chai only when you have a clear architectural reason to build your own testing stack.
Choose chai only if you are already using a test runner like mocha and are unhappy with the default assertions. It provides rich assertion styles (expect, should, assert) that make tests more readable. Do not use this as a standalone tool; it must be paired with a test runner.
Choose jasmine primarily for maintaining legacy AngularJS applications or environments where it is already deeply integrated. For new projects, it is generally recommended to use jest instead, as jasmine lacks some of the modern developer experience features like snapshot testing and fast parallel execution found in jest.
Choose jest for new React, Node, or TypeScript projects where you want a zero-config setup that works immediately. It is the industry standard for frontend development due to its built-in mocking, snapshot testing, and watch mode. Use this if you prefer convention over configuration and want a single tool to handle running, asserting, and mocking.
Choose mocha if you need maximum flexibility in how your tests are structured or if you are working in a non-standard environment where jest might struggle. It is ideal when you want to pick and choose your own assertion library (like chai) and mocking tools. Use this for legacy projects or complex integration tests that require custom reporters.
Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.
For more information or to download plugins, view the documentation.
Chai is an assertion library, similar to Node's built-in assert. It makes testing much easier by giving you lots of assertions you can run against your code.
chai is available on npm. To install it, type:
$ npm install --save-dev chai
You can also use it within the browser; install via npm and use the index.js file found within the download. For example:
<script src="./node_modules/chai/index.js" type="module"></script>
Import the library in your code, and then pick one of the styles you'd like to use - either assert, expect or should:
import { assert } from 'chai'; // Using Assert style
import { expect } from 'chai'; // Using Expect style
import { should } from 'chai'; // Using Should style
import 'chai/register-assert'; // Using Assert style
import 'chai/register-expect'; // Using Expect style
import 'chai/register-should'; // Using Should style
import { assert } from 'chai'; // Using Assert style
import { expect } from 'chai'; // Using Expect style
import { should } from 'chai'; // Using Should style
should(); // Modifies `Object.prototype`
import { expect, use } from 'chai'; // Creates local variables `expect` and `use`; useful for plugin use
mocha spec.js --require chai/register-assert.js # Using Assert style
mocha spec.js --require chai/register-expect.js # Using Expect style
mocha spec.js --require chai/register-should.js # Using Should style
Read more about these styles in our docs.
Chai offers a robust Plugin architecture for extending Chai's assertions and interfaces.
chai-pluginbrowser if your plugin works in the browser as well as Node.jsbrowser-only if your plugin does not work with Node.jsError constructor thrown upon an assertion failing.Thank you very much for considering to contribute!
Please make sure you follow our Code Of Conduct and we also strongly recommend reading our Contributing Guide.
Here are a few issues other contributors frequently ran into when opening pull requests:
chai.js build. We do it once per release.Please see the full Contributors Graph for our list of contributors.
Feel free to reach out to any of the core contributors with your questions or concerns. We will do our best to respond in a timely manner.
This project would not be what it is without the contributions from our prior core contributors, for whom we are forever grateful: