chai, enzyme, jest, mocha, and react-testing-library are foundational tools in the JavaScript testing ecosystem, but they serve different purposes. jest and mocha are test runners that execute test suites and report results. chai is an assertion library used to write expressive test expectations. enzyme (now deprecated) and react-testing-library are React-specific utilities for rendering and interacting with components during tests. Modern best practices favor jest as a zero-config runner with built-in assertions and mocking, paired with react-testing-library for user-centric React testing, while mocha and chai remain viable for non-React or highly customized testing setups.
Choosing the right testing tools is critical for building maintainable, reliable frontend applications. The landscape includes assertion libraries, test runners, and React-specific utilities — each serving distinct roles. Let’s break down how chai, enzyme, jest, mocha, and react-testing-library fit into modern testing workflows, with real code examples and practical guidance.
It’s important to understand that these packages don’t all solve the same problem. Some are test runners, others are assertion libraries, and a few are React component testing utilities.
jest is a full-featured test runner with built-in assertions, mocking, and snapshot testing.mocha is a flexible test runner that requires separate assertion and mocking libraries.chai is an assertion library often paired with mocha (or other runners).enzyme is a utility for testing React components by shallow rendering or full DOM rendering (now largely deprecated).react-testing-library is a React testing utility focused on user-centric component interaction.You typically combine a runner (jest or mocha) with an assertion library (chai or Jest’s built-ins) and optionally a React testing helper (react-testing-library or, historically, enzyme).
How you write your expectations depends heavily on your chosen assertion library.
chai supports both BDD (expect, should) and TDD (assert) styles:
// chai with expect (BDD)
expect(result).to.equal(42);
expect(list).to.include('item');
// chai with assert (TDD)
assert.equal(result, 42);
assert.include(list, 'item');
jest has its own built-in assertion API, inspired by Jasmine:
// jest built-in
expect(result).toBe(42);
expect(list).toContain('item');
mocha doesn’t include assertions — you must bring your own (like chai):
// mocha + chai
const { expect } = require('chai');
describe('example', () => {
it('works', () => {
expect(add(2, 2)).to.equal(4);
});
});
react-testing-library doesn’t provide assertions — it returns DOM elements that you assert on using your test framework’s matcher (e.g., Jest’s expect):
// react-testing-library + jest
const { getByText } = render(<Button label="Click me" />);
expect(getByText('Click me')).toBeInTheDocument();
enzyme included its own assertion helpers via .find(), .text(), etc., but still required an external assertion library:
// enzyme + chai (deprecated pattern)
const wrapper = shallow(<Button label="Submit" />);
expect(wrapper.find('button').text()).to.equal('Submit');
⚠️ Important: As of February 2023, Enzyme is officially unmaintained. The team recommends migrating to
react-testing-library. Do not use Enzyme in new projects.
jest includes everything out of the box:
jest.fn(), jest.mock())// jest mock example
jest.mock('./api');
api.fetchUser.mockResolvedValue({ id: 1, name: 'Alice' });
mocha is minimal by design. You add what you need:
sinon)--watch)// mocha + sinon mock
const sinon = require('sinon');
const apiStub = sinon.stub(api, 'fetchUser').resolves({ id: 1 });
Both runners support hooks like beforeEach, afterAll, etc., but jest’s global setup is more streamlined for frontend apps.
This is where philosophy matters most.
enzyme encouraged testing implementation details:
// enzyme (discouraged today)
const wrapper = shallow(<MyComponent />);
expect(wrapper.state('loading')).to.be.true;
expect(wrapper.find('ChildComponent').props().value).to.equal('test');
This tightly couples tests to internal component structure, making refactors painful.
react-testing-library promotes testing like a user:
// react-testing-library (recommended)
const { getByRole, getByLabelText } = render(<LoginForm />);
const emailInput = getByLabelText('Email');
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
const submitButton = getByRole('button', { name: /submit/i });
fireEvent.click(submitButton);
await waitFor(() => expect(screen.getByText('Success!')).toBeInTheDocument());
You interact with elements the way users do — by role, label, or text — not by class names or component hierarchy.
In practice, you’ll see two dominant stacks:
jestexpect@testing-library/react✅ Recommended for new React projects. Fully supported, aligned with React team guidance.
// Full example
import { render, screen, fireEvent } from '@testing-library/react';
import MyForm from './MyForm';
test('submits form with valid input', async () => {
render(<MyForm />);
fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'John' } });
fireEvent.click(screen.getByRole('button', { name: /save/i }));
expect(await screen.findByText('Saved!')).toBeInTheDocument();
});
mochachaisinonenzyme, now migrated to react-testing-library✅ Still viable for non-React projects or legacy systems where Jest isn’t feasible.
// mocha + chai + react-testing-library
const { expect } = require('chai');
const { render, screen } = require('@testing-library/react');
describe('Header', () => {
it('displays logo', () => {
render(<Header />);
expect(screen.getByAltText('Company logo')).to.exist;
});
});
Note: Even with mocha, you can (and should) use react-testing-library instead of enzyme.
As confirmed by Enzyme’s official GitHub README:
“Enzyme is no longer under active development. We recommend using React Testing Library instead.”
The project hasn’t kept pace with React 16.3+ features like hooks, suspense, or concurrent rendering. Tests written with Enzyme may pass while the actual app breaks in production due to mismatched rendering models.
| Scenario | Recommended Stack |
|---|---|
| New React app | jest + react-testing-library |
| Non-React JS project needing flexibility | mocha + chai + sinon |
| Legacy React app using Enzyme | Migrate to react-testing-library; keep jest or mocha as runner |
| Need advanced mocking or snapshot testing | jest (built-in) |
| Prefer explicit control over test toolchain | mocha + pluggable ecosystem |
jest and react-testing-library. This combo is endorsed by the React team and scales well from simple components to complex user flows.mocha + chai remains a solid, modular choice — especially if you want to avoid Jest’s opinionated defaults.enzyme. The maintenance gap makes it a liability.chai is unnecessary with jest — Jest’s assertions are sufficient and better integrated.Testing isn’t just about verifying code — it’s about enabling confident refactoring and catching regressions early. Choose tools that encourage testing what users experience, not how your components are built internally. That mindset shift, supported by react-testing-library and jest, pays dividends as your app grows.
Choose chai if you're using a test runner like mocha or tape that doesn't include its own assertion library, and you prefer expressive BDD/TDD syntax (expect, should, or assert). It’s unnecessary when using jest, which provides a robust built-in assertion API. Avoid adding chai solely for style preferences — consistency with your existing toolchain matters more.
Choose jest for most new JavaScript or TypeScript projects, especially React apps. It provides zero-config setup, built-in mocking, snapshot testing, code coverage, and a powerful watch mode out of the box. Its integrated assertion library reduces dependency bloat, and its compatibility with react-testing-library makes it the de facto standard for modern frontend testing.
Choose mocha when you need maximum flexibility in your testing stack, such as in non-React Node.js applications or environments where Jest’s opinionated defaults (e.g., automatic mocking, JSDOM setup) are undesirable. Pair it with chai for assertions and sinon for spies/stubs. Be prepared to configure coverage, mocking, and module resolution manually.
Do not choose enzyme for new projects. It is officially unmaintained as of 2023 and incompatible with modern React features like hooks, suspense, and concurrent rendering. If you maintain a legacy codebase using enzyme, prioritize migration to react-testing-library. Its focus on implementation details (e.g., state, props, shallow rendering) leads to brittle tests that hinder refactoring.
Choose react-testing-library for all new React component tests. It encourages testing components the way users interact with them — by querying accessible roles, labels, and text — rather than implementation details. It works seamlessly with jest but also integrates with mocha. Adopt it even in legacy projects to improve test reliability and enable safe refactoring.
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: