jest, mocha, jasmine, chai, and sinon form the core ecosystem for JavaScript testing, but they serve different roles. jest is an all-in-one framework handling runners, assertions, and mocks. mocha is a flexible test runner that pairs with chai for assertions and sinon for spies. jasmine is an older all-in-one solution often used in Angular projects. chai provides flexible assertion styles, while sinon specializes in standalone spies, stubs, and mocks. Understanding their distinct responsibilities helps teams build a maintainable testing strategy.
Building reliable software requires a solid testing strategy. The JavaScript ecosystem offers several tools for this purpose, but they do not all do the same thing. jest, mocha, jasmine, chai, and sinon are the most common names you will encounter. Some are test runners, some check results, and others fake dependencies. Let's break down how they fit together and when to use each one.
The test runner is the engine that finds and executes your test files. It manages the lifecycle, reporting, and parallelization.
jest includes a built-in runner that works out of the box.
.test.js.// jest: Built-in runner
// math.test.js
const sum = require('./math');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
mocha is a dedicated runner that needs configuration to know where tests live.
// mocha: Configured runner
// test/math.test.js
const sum = require('../math');
describe('Math', () => {
it('adds 1 + 2 to equal 3', () => {
// Needs assertion lib like Chai
if (sum(1, 2) !== 3) throw new Error('Failed');
});
});
jasmine also includes a runner but relies on a spec helper file.
spec_dir in jasmine.json.// jasmine: Spec runner
// spec/math.spec.js
const sum = require('../src/math');
describe('Math', () => {
it('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
});
chai and sinon do not run tests.
// chai/sinon: No runner
// These libraries provide assertions and spies
// They must be imported into a runner like Mocha or Jest
import { expect } from 'chai';
import sinon from 'sinon';
Assertions verify that your code behaves as expected. Different libraries offer different syntax styles.
jest uses a global expect function with chainers.
// jest: Global expect
const user = { name: 'Alice', active: true };
expect(user.name).toBe('Alice');
expect(user).toHaveProperty('active', true);
expect(user).toMatchObject({ name: 'Alice' });
chai offers three styles: expect, should, and assert.
expect is similar to Jest but requires import.should modifies object prototypes (less common now).// chai: Expect style
import { expect } from 'chai';
const user = { name: 'Alice', active: true };
expect(user.name).to.equal('Alice');
expect(user).to.have.property('active', true);
expect(user).to.include({ name: 'Alice' });
jasmine uses a global expect similar to Jest.
// jasmine: Global expect
const user = { name: 'Alice', active: true };
expect(user.name).toBe('Alice');
expect(user).toBeDefined();
expect(user.active).toBeTruthy();
mocha and sinon do not provide assertions.
mocha relies on chai or Node's built-in assert.sinon focuses on behavior verification, not value checking.// mocha/sinon: No assertions
// Mocha throws errors on failure, Sinon verifies calls
// You typically import Chai for value checks
import { expect } from 'chai';
expect(true).to.be.true;
Sometimes you need to isolate code by faking external calls like APIs or timers.
jest has built-in mocking functions.
jest.fn() creates a spy easily.jest.mock() hoists module mocks to the top.// jest: Built-in mocks
const mockFn = jest.fn();
mockFn('hello');
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('hello');
jest.mock('axios');
sinon is the gold standard for standalone spies and stubs.
// sinon: Standalone spies
import sinon from 'sinon';
const obj = { method: () => {} };
const spy = sinon.spy(obj, 'method');
obj.method();
sinon.assert.calledOnce(spy);
sinon.restore();
jasmine includes basic spying tools.
spyOn works on existing object methods.// jasmine: Built-in spies
const obj = { method: () => {} };
spyOn(obj, 'method').and.callThrough();
obj.method();
expect(obj.method).toHaveBeenCalled();
chai and mocha do not mock dependencies.
sinon or jest mocks for this.chai can verify Sinon spies via plugins.// chai/mocha: No mocking
// Use Sinon plugin for Chai to assert spy behavior
import chai from 'chai';
import sinonChai from 'sinon-chai';
chai.use(sinonChai);
expect(spy).to.have.been.called;
Setup time varies significantly between these tools.
jest aims for zero config.
jest.config.js.// jest: Minimal config
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
coverageDirectory: 'coverage'
};
mocha requires manual wiring.
.mocharc.js or command line.// mocha: Manual config
// .mocharc.js
module.exports = {
extension: ['js'],
spec: 'test/**/*.test.js',
require: ['@babel/register']
};
jasmine needs a spec helper.
jasmine init.spec/support/jasmine.json.// jasmine: Spec config
// spec/support/jasmine.json
{
"spec_dir": "spec",
"spec_files": ["**/*[sS]pec.js"]
}
chai and sinon are imports only.
// chai/sinon: Import only
// No config file, just install and import
import chai from 'chai';
import sinon from 'sinon';
Despite their differences, these tools share common goals and patterns.
async/await and Promises.// jest
test('fetches data', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
// mocha
it('fetches data', async () => {
const data = await fetchData();
// assert with Chai
});
// jasmine
it('fetches data', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
beforeEach, afterEach for clean state.// jest
beforeEach(() => { /* setup */ });
afterEach(() => { /* cleanup */ });
// mocha
beforeEach(function() { /* setup */ });
afterEach(function() { /* cleanup */ });
// jasmine
beforeEach(() => { /* setup */ });
afterEach(() => { /* cleanup */ });
// All: TypeScript support
// tsconfig.json includes test files
// jest types: @types/jest
// mocha types: @types/mocha
// chai types: @types/chai
| Feature | jest | mocha | jasmine | chai | sinon |
|---|---|---|---|---|---|
| Test Runner | β Built-in | β Built-in | β Built-in | β | β |
| Assertions | β Built-in | β (Needs Chai) | β Built-in | β Standalone | β |
| Mocking | β Built-in | β (Needs Sinon) | β Basic | β | β Standalone |
| Config Effort | π’ Low | π High | π Medium | π’ None | π’ None |
| Best For | React/General | Custom Node | Legacy/Angular | Custom Assertions | Complex Mocks |
| Stack | Components | Use Case |
|---|---|---|
| Modern Default | jest only | React, Vue, general JS apps |
| Classic Flexible | mocha + chai + sinon | Complex Node.js, custom needs |
| Legacy Angular | jasmine + karma | Older Angular projects |
| Assertion Focus | mocha + chai | When you only need custom assertions |
| Mocking Focus | jest + sinon | When Jest mocks aren't enough |
jest is the default choice for most teams today. It removes the friction of stitching tools together. If you are starting a new React or general JavaScript project, this is usually the right pick.
mocha + chai + sinon is the power user stack. It requires more setup but gives you full control over every layer. Choose this if you have specific requirements that Jest cannot meet.
jasmine is stable but aging. It still powers many Angular projects, but new projects rarely choose it over Jest.
Final Thought: Don't overcomplicate your setup. Start with jest unless you have a clear reason to build a custom stack. Testing should support your development β not slow it down.
Choose chai if you need flexible assertion styles like expect, should, or assert within a custom test runner setup. It is ideal when paired with mocha for projects requiring specific assertion syntax that other frameworks do not support. Use it when you want to mix and match testing tools rather than adopting a full suite.
Choose jasmine if you are maintaining legacy Angular projects or prefer a batteries-included framework without external dependencies. It works well for teams that want a stable, opinionated structure without the complexity of configuring multiple libraries. Avoid it for new React projects where jest offers better integration and community support.
Choose jest for most modern JavaScript projects, especially React applications, due to its zero-config setup and all-in-one capabilities. It handles test running, assertions, and mocking out of the box, reducing the need for multiple dependencies. It is the best choice for teams prioritizing developer speed and strong ecosystem integration.
Choose mocha if you need maximum flexibility to configure your test runner and select your own assertion and mocking libraries. It is suitable for complex Node.js environments where custom reporting or specific async control is required. Use it when jest feels too opinionated or restrictive for your architecture.
Choose sinon when you need powerful standalone spies, stubs, and mocks that work across any test runner. It is essential for testing legacy code or complex dependencies where built-in mocking tools fall short. Pair it with mocha and chai for a classic, highly customizable testing stack.
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: