Murat Çatal I have over nine years of hands-on development experience as a fullstack developer, including technical lead positions. Currently overseeing development of enterprise applications, leading the frontend team.

Comparing React testing libraries

15 min read 4365

Comparing React Testing Libraries

Editor’s Note: This post comparing React testing libraries post was last updated on 8 February 2023 to add sections on testing with Mocha, Chai, and TestCafe. This update also includes updates to the code and a new section on the best React testing library. To learn more about testing, check out this guide

If you create something, no matter what, you should test it before serving it to others. This helps developers assess the stability of their projects. That way, you can have more confidence and control over your finished product before you roll it out. Let’s take a look at some React testing strategies.

Jump ahead:

React testing strategies

There are several ways to test React applications, from little code blocks to more general aspects. Before we dive into the frameworks and libraries associated with them, let’s examine some of the most useful methods to evaluate the functionality of your React app.

Unit tests

A unit test is a test that examines each small piece of your code. You may think of it as testing primitive components in their lifecycles. This is often the simplest and least expensive testing option. One example of a unit test can be to check whether a validation function (our unit) returns an expected output.

Integration tests

If you have many composed components, you may want to test how they interact. You can do this by mocking your endpoints as part of an integration test. However, this can be costlier and more complex than unit testing. Here, a common use case would be to analyze whether multiple units, for example, an authentication and a registration component in a shopping website, work together properly.

End-to-end testing

End-to-end testing is your best bet when testing the entire system with real data to see whether everything works as expected. One notorious use case for this type of test is when developers want to ensure their application UI (the frontend) and their database (the backend) work properly with one another.

You may be tempted to tinker with your component’s internal business and test implementation details when you start writing your test. Be careful! This will lead you down the wrong path. Instead, you should write tests from the user’s perspective to generate cleaner and more accurate test cases.

After all, your end users are not interested in your component’s internal details but in what they see. Now that we’ve established some general best practices let’s take a closer look at some of the most common testing frameworks and runners. We’ll examine the learning curves, capabilities, and pros and cons associated with each.

Testing React apps with Jest

Jest is a testing framework created and maintained by Facebook. If you build your React application with Create React App, you can start using Jest with zero config. Just add react-test-renderer and the @testing-library/react library to conduct snapshot and DOM testing.

With Jest, you can:

  • Conduct snapshot, parallelization, and async method tests
  • Mock your functions, including third-party node_module libraries
  • Execute myriad assertion methods
  • View code coverage report

Now, let’s get our hands dirty with some code. Let’s assume your application is created via CRA:

# For snapshot test
yarn add -D react-test-renderer

# For DOM test
yarn add -D @testing-library/react

For an existing application that is not built with CRA, first, add these dependencies:

yarn add --dev jest babel-jest @babel/preset-env 

Then, configure Babel so that it uses your Node.js installation, like so:

// create a file called babel.config.js in the root of your project:
module.exports = {
presets: presets: [['@babel/preset-env', {targets: {node: 'current'}}]]
Add the testing command in your package.json.
// package.json
"scripts": {
"test": "jest"

This will tell Yarn that every time you execute the yarn test command, Jest will run to perform tests on your web app.

Setting up the Jest testing structure

Now that you’ve added test files to your application, let’s dive into more details about the testing structure. As shown below, CRA has been configured to run tests that have .spec.js and .test.js files:

// MyComponent
export const MyComponent = ({ label }) => {
  return <div>{label}</div>;

We have a simple component that takes a label prop and displays it on the screen:

The next step is to write a small test to ensure that it displays properly, as shown below:

import React from "react";
import { cleanup, render } from "@testing-library/react";
import { MyComponent } from "./MyComponent";
// @testing-library/react -> DOM testing
// react-test-renderer -> snapshot testing
afterEach(() => {
describe("MyCompnent", () => {
  test("should display label", () => {
    const { getByText } = render(<MyComponent label="Test" />);

Now, let’s review the features we want to test — afterAll and beforeAll.

Testing our features

The afterAll method will run code after the tests are completed in the current test file. On the other hand, beforeAll will run just before your test starts. You can clean up your resources and mock data created on the database by using the afterAll function, or you can set up your configurations in beforeAll.

That function may return a generator or a promise and wait for your promise or generator function to finish its execution before it continues. Here’s an example:

// MyTestFile.test.js
afterAll(() => {
  cleanResources(); //clean all render data. This will prevent memory leaks.

beforeAll(() => {
   setupMyConfig(); //example: set some global state so that it can be shared with multiple test files.

describe("MyComponent",() => {
   test("should do this..",() => {

afterAll runs when all your tests finish their executions in the current file. Unlike afterAll and beforeAll, afterEach and beforeEach are called for each test case in your test file. By using beforeEach, you can create a connection to your database before each test case starts to run.

As a best practice, you should use afterAll to remove your created DOM elements after each test case run:

// MyTestFile.test.js
afterAll(() => {

beforeAll(() => {

describe("MyComponent",() => {
   test("should do this..",() => {
//create another test:
   test("should do that..",() => {

The describe command allows you to group related tests to produce cleaner outputs. Here’s what it will look like:

describe("MyComponent",() => {
   test("should do this..",() => {

   test("should do that..",() => {

To run the test, we can use the command npm run test or yarn run test in our terminal. However, if you are on CodeSandbox, you can use the test button, as shown in the image below:

React Testing Example

Snapshot testing with Jest

A snapshot test generates an HTML-like output to see how your component is structured. It’s especially useful if you want to see the structure of your HTML or how your CSS properties are injected according to events. Essentially, a __snapshot__ folder is automatically created when npm run test command is executed in the terminal.

To get started, you can clone this repository that I created to demonstrate how snapshot test works:

// Link.jsx
import React from "react";
const Link = ({ page }) => {
  return (
      onMouseEnter={() => console.log("Mouse enter")}
      onMouseLeave={() => console.log("Mouse leave")}
      My Domain
export default Link;

// React / JavaScript
// App.js
import React from 'react';
import './index.css';
import Link from './Link';
export default function App() {
  return (
    <div className='App'>
      <h1>Hello Codes</h1>
      <Link page='' />

// React / JavaScript
// Link.test.js

import renderer from 'react-test-renderer';
import Link from './Link';
it('renders correctly', () => {
  const tree = renderer
    .create(<Link page=''>Bonarhyme</Link>)

// React / JavaScript
// Generated snapshot
// __snapshot__/Link.test.js.snap

// Jest Snapshot v1,

exports[`renders correctly 1`] = `
  My Domain

In the code above, the Link.js contains an anchor tag that we will be testing against while the App.js hosts the components. The Link.test.js holds our test. Essentially, we are comparing the generated snapshot with what we expect it to be. Interestingly, the result will be written to __snapshot__/Link.test.js.snap file.

Mocking functions

Mocking while testing is one of the core features you will need to implement. The good news is that Jest is great for mocking your functions and modules.

For example, let’s say you want to test a function that fetches users. It uses Axios, but we don’t want to hit a real endpoint because that’s not what we want to test. Here’s our code:

import axios from 'axios';

const CustomersList = [
{name: 'Bob'}, 
{name: 'Jenny'},
{name: 'Philip'},
{name: 'Casandra'}


test('should fetch users', () =>; {
  const customers = [{name: 'Bob'}, {name: 'Jenny'}];
  const resp = {data: customers.find(c =>; = 'Bob')};

  return CustomersList.getByFilter("Bob").then(data => expect(data).toEqual({name: 'Bob'}));

In the code above, Jest can serve a wide range of purposes, such as mocking an API call. Consequently, in the example above, we simulate an API call and compare if the returned data from the call matches what we provided in the CustomersList. We achieve that by using axios.get.mockResolvedValue(resp); and CustomersList.getByFilter("Bob").then(data => expect(data).toEqual({name: 'Bob'}));.

Testing with Jasmine

Like Jest, Jasmine is a JavaScript framework and test runner. However, you should add some configuration before you start using Jasmine.

Here are some neat things you can do with Jasmine:

  • Async function tests
  • Mocking requests
  • Custom equality checker assertion
  • Custom matcher assertion

As for drawbacks, here are some things Jasmine does not support:

  • Snapshot tests
  • Code coverage tools
  • Parallelization (requires third-party tools)
  • Native DOM manipulation (requires a third-party tool such as JSDOM)

In addition, Jasmine looks for only .spec.js files, so you need to edit its configuration to look for .test.js files, too.

Jasmine is mostly used with Enzyme, so you will need to install it and make some configurations like so:

yarn add -D babel-cli \
            @babel/register \
            babel-preset-react-app \
            cross-env \
            enzyme \
            enzyme-adapter-react-16 \
            jasmine-enzyme \
            jsdom \

Then, initialize your project for Jasmine with the yarn run jasmine init command. Now, we’ll put some configuration files in a spec/helper folder. They will be for Babel, Enzyme, and JSDOM:

// babel.js

// for typescript
    "extensions": [".js", ".jsx", ".ts", ".tsx"]

// enzyme.js or enzyme.ts 
// be sure your file extension is .ts if your project is a typescript project
import jasmineEnzyme from 'jasmine-enzyme';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

beforeEach(function() {
  jasmineEnzyme(); //before each test, setup our jasmine config.

// jsdom.js

import {JSDOM} from 'jsdom';

const dom = new JSDOM('<html><body></body></html>');
global.document = dom.window.document;
global.window = dom.window;
global.navigator = dom.window.navigator;

Lastly, edit the Jasmine configuration file to ensure the Babel, Enzyme, and JSDOM configs are correctly loaded. Now, it’s time to move into spec/support/jasmine.json:

// the important part here is we should load babel first.

// for normal projects
"helpers": [

// for typescript projects
"helpers": [

Let’s review how we write a test with Jasmine. We will also touch upon Enzyme.

Most of the helper functions, such as afterAll, beforeAll, afterEach, and beforeEach, are similar to Jest, so let’s dig into how to write a basic test for a React component to see its structure:

const Utils = React.addons.TestUtils;
let element;
beforeEach(() => {
  element = React.createElement(
        label: 'Hello'

afterEach(() => {
  element = null;

describe('MyComponent', function() {
  it('can render without error', function() {
    const component = Utils.renderIntoDocument(element);

To run it, run the jasmine.spec.js command.

Using custom matchers with Jasmine

In Jasmine, you can write customMatcher functions to reuse globally in each test spec. A custom matcher could come in handy if, for example, you have a specified group of testing matchers that are used frequently.

Custom matchers should return an object that has pass and message properties. A pass property checks that conditions are in a valid state, while message is the field that is shown in a failed state. Here’s what that looks like:

const customMatchers = {
  toBeValidAgeRange: function() {
    return {
      compare: function(actual, expected) {
         var result = {};
         result.pass = (actual > 18 && actual <=35);
         result.message = actual + ' is not valid';   
         return result;
describe("Custom matcher", function() {
  beforeEach(function() {
    // register our custom matcher with Jasmine
  it("should be valid age", function() {

  it("should fail", function() {

Implementing a custom equality checker

Sometimes, you may need to compare two objects or change the behavior of equality checking to compare primitive types. Jasmine has a good API for overriding equality checking. The custom checker function must have two parameters: the first comes from expect and the second comes from the assertion function. Also, it must return Boolean or undefined.

If it returns undefined, the equality function is unsuitable for these parameters. Here’s an example:

function myObjectChecker(first, second) {
//check if they both are objects and have the 'name' field  
  if (typeof first === 'object' && typeof second === 'object' && 
      first.hasOwnProperty('name') && second.hasOwnProperty('name')) {
    return ===;

beforeEach(() => {
//now register your tester with Jasmine so that we can use it.  

describe('MyComponent', function() {
  it('can render without error', function() {
    expect({name: 'John'}).toEqual({name:'John'}); //will pass the test
  it('Not equal using a custom tester.', function() {
    expect({name: 'John'}).not.toEqual({age:19}); //will pass the test.

Using react-testing-library to test React applications

Created by Kent C. Dodds and maintained by a huge community of developers, the react-testing-library enables you to test components without touching their internal business — which empowers you to conduct more powerful test cases while keeping the user experience top of mind.

With react-testing-library, you can:

  • Query your elements within the text, label, displayValue, role, and testId
  • Fire any event
  • Wait for an element to appear with wait

However, you cannot:

  • Conduct shallow rendering
  • Access the internal business of your components, such as states

Install the library with the yarn add -D @testing-library/react command. Now, for the fun part…

import React from 'react';
import { render, RenderOptions, RenderResult, fireEvent, screen} from '@testing-library/react';

describe('MyComponent', () => {
  test('Click on item', () => {

    render() //render the component to the DOM"Click me")); //find the button and click it.
    expect(screen.getByRole('button')).toBeDisabled() //if the button is disabled, pass the test.

In the code above, we began by installing @testing-library/react as a dev dependency. Next, we imported the necessary packages and used the describe method to simulate a button click with Click me as the text content in our app. We also ensured that the test passed if the button was disabled. You can see the full API library here.

Testing React components with Enzyme

Enzyme is a JavaScript testing utility framework designed to help developers test React components easily. It’s maintained by Airbnb and is among the most used frameworks.

Enzyme enables you to:

  • Use shallow rendering
  • Access business implementations of your components
  • Conduct full DOM rendering
  • Use react-hooks in shallow rendering, with some limitations

Here’s a handy guide if you want to compare Enzyme to react-testing-library in-depth. Get started by installing the following:

yarn add -D enzyme enzyme-adapter-react-16

Then, create an enzyme.js in src folder, as shown below:

import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;
Now, let's do some coding. Here's how we're going to get started with shallow rendering:
import React from 'react';
// we are importing from our enzyme.js
import { shallow } from './enzyme';

import MyComponent from './MyComponent';

describe('MyComponent', () => {
  test('renders correct text in item', () => {
    const wrapper = shallow(<MyComponent label="Hello" />);

    //Expect the child of the first item to be an array

We can also do full DOM rendering:

describe('<Foo />', () =>; {
  it('calls componentDidMount', () =>; {
    sinon.spy(Foo.prototype, 'componentDidMount');
    const wrapper = mount(<Foo />);
    expect(Foo.prototype.componentDidMount)'callCount', 1);

Beware of componentDidMount! We accessed the internal business of our component, which may lead you to write incorrect test cases if you’re not careful.

React testing with Mocha

Mocha is a JavaScript testing library that can be used to run asynchronous tests. To understand how it works, we will clone the following repository.

Generally, our test files will exist inside the test folder, and each file should have the .spec.ts extension and match the name of the app the test is for. Our App.js contains the following content: App.js content. We can test for the presence of certain keywords, such as the text returned in h1. We can open the App.spec.ts to test like this:

// App.spec.ts

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import Enzyme from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import App from '../src/app.js';
Enzyme.configure({ adapter: new Adapter() });

describe('<App />', () => {
  it('renders our app with welcome text', () => {
    const wrapper = shallow(<App />);
    expect(wrapper.text()).to.equal("Welcome to the React application.")

Finally, we can run npm test to run our test.

Testing with Chai

Chai is an assertion library that is used in many testing libraries like Mocha. To start using Chai, we need to install it into our project using npm install chai or using the installation method of our choice.

Generally, there are a number of assertion styles that we can use. They include Expect, Assert, and more. You can see a full list here.

A good example of using the Assert style is to check if an array includes a specific value like so:

// test.js
// Assert style
var assert = require('chai').assert;
var people = ["boy", "girl", "man", "woman", "ladies"];

assert.isArray(people, 'is array of strings');
assert.include(people, 'man', 'array contains man');
assert.lengthOf(people, 5, 'array contains 5 strings');

Performing end-to-end tests in React apps

Up to this point, we’ve examined testing libraries in terms of writing unit or integration tests. However, we may also need a fully integrated test with the backend before going to production. For that purpose, we will look at two libraries: Cypress and Puppeteer.


Cypress enables you to write your tests without any additional testing framework. It has a nice API to interact with page components and supports many browsers, including Firefox and other Chrome-based browsers.

Here’s what you can do with Cypress:

  • Time travel
  • Screenshots and videos
  • Automatic waiting
  • Control network traffic without touching your server to test edge cases
  • Parallelization

Use the following lines of code to install and run Cypress, respectively:

yarn add -D cypress
yarn run cypress open

Now, let’s write some tests. First, create a file named my-test_spec.js:

// my-test_spec.js

describe('My First Test', function() { //first, describe your test
  it('Gets, types and asserts', function() {
    cy.visit('') //visit your app. Can be localhost.
    cy.contains('login').click() //find the button that contains 'login'

    cy.url().should('include', '/login')

    cy.get('.email') //find textbox with class 'email'. Cypress uses jQuery selectors.
      .type('[email protected]')
      .should('have.value', '[email protected]')


Puppeteer is not a JavaScript testing framework — it’s a headless Chromium library mostly used for automation purposes. You can start your Chromium and, with the provided API, navigate between pages, get buttons, and click them.

Puppeteer runs on a real browser and enables you to write your end-to-end tests with an API similar to the browser. To install, enter the following line of code:

yarn add -D jest-puppeteer puppeteer jest

Then, enter the following in package.json:

// package.json
 jest: {
    "preset": "jest-puppeteer"

Below is the code for our end-to-end testing:

beforeAll(async ()=> {
  await page.goTo('');

describe('Visit MyDomain', () => {
  test('should have login text', () => {
     await expect(page).toMatch('login');


TestCafe is another end-to-end testing library for web applications built with React and other frameworks. It works differently from other testing libraries in the sense that it is used to simulate common user scenarios in major desktop browsers, cloud browsers, and on mobile devices.

To start using TestCafe, you should have it installed on your computer globally using the npm install -g testcafe command. Also, you should install testcafe-react-selectors used to select React components from a page using the npm install -g testcafe-react-selectors command.

Next, you create a getting-started.js file and open it in your favorite code editor. For the purpose of demonstration, I will start this React app on my computer here. Next, I will add the following code to test if a component named Link exists:

// getting-started.js
import { waitForReact, ReactSelector } from 'testcafe-react-selectors';

// This is where we pass the URL of the app and also await React to load the page completely
fixture`App tests`.page('http://localhost:3000').beforeEach(async (t) => {
  await waitForReact(90000, t);

// We can perform any test here...
test('My first test', async (t) => {
  const AppLink = ReactSelector('Link');
  await t
    .eql('My Domain');

Finally, we can then run the testcafe edge getting-started.js command in our terminal

Note: You can use any browser of your choice. Here are some of the options to choose from

Comparing React testing libraries and frameworks

Until now, we looked at the features of libraries and how to implement them in our projects. Now, let’s examine some benchmarks and compare the results among libraries.

Jest vs. Jasmine

As mentioned in the beginning, Jest and Jasmine are testing frameworks. You group your test cases within describe blocks and write your tests within test or it functions.

Now, let’s break down our comparison in a handy, easy-to-read table:

Comparing React Testing Frameworks: Jest Vs. Jasmine

Here’s what I like most about Jest:

  • Zero configuration required
  • Snapshot testing support
  • Code coverage support
  • Mocking functions

As for Jasmine, the most useful feature is its mocking function. Though this is somewhat limited, it is sufficient for most use cases. I am currently using Jest in a product due to its native support within the React community and because it serves our needs in terms of testing React components more so than Jasmine.

react-testing-library vs. Enzyme

Your functions are among the most important considerations when writing tests for a component. They may force you to write a cleaner and truer way of testing or lead you to write your tests incorrectly regarding exported APIs. Let’s look at react-testing-library and Enzyme head-to-head:

Comparing React Testing Libraries: react-testing-library Vs. Enzyme

Don’t get too bogged down in the implementation details when writing tests for your components. Remember, try to think about it from the user’s perspective. This will help you produce better test suites, which will help you feel more confident about your tests.

For most use cases, I prefer react-testing-library primarily because its exported APIs do not allow you to use a component’s internal API, forcing you to write better tests. In addition, there is zero configuration required.

Enzyme, on the other hand, lets you use a component’s internal API, which can include lifecycle methods or state. I’ve used both Enzyme and react-testing-libraries in many projects. However, I’ve often found that react-testing-library makes things easier.

Cypress vs. Puppeteer

Testing your critical pages end-to-end may save you a headache before going to production. Below is a summary comparison of Cypress and Puppeteer:

Comparing React Testing Libraries: Cypress Vs. Puppeteer

Because Cypress is a testing framework, it has many advantages over Puppeteer when the things you want to develop need to be fast. Its developer-friendly APIs enable you to write tests like you would a unit test. Puppeteer is not a testing framework and is a browser. Its APIs are not developer-friendly but powerful because you can access the browser’s API. Therefore, it comes with a steeper learning curve than Cypress.

Cypress vs. TestCafe

Both Cypress and TestCafe are excellent testing libraries. But, as with every other technology out there, there are pros and cons attached to them. Let’s look at them:

TestCafe Cypress
File Upload Feature It supports file upload out-of-the-box It relies on third-party solutions
Documentation Documentation is amazing Documentation is amazing
Multiple Window / Tab Support Supports many tabs and windows open at once It doesn’t natively support many tabs and windows open at once
Libraries for Assertion It uses built-in assertion libraries It relies on Chai for an assertion library
Ease of use It is easy to setup It is easy to use


As you can see, each testing method, library, and framework brings its own advantages and shortfalls, depending on the use case and types of data you wish to analyze.

After evaluating each testing framework with these factors in mind, it’s clear that react-testing-library is the most valuable and logical choice for unit and integration tests. For end-to-end testing, Cypress is an appropriate choice for its easy-to-learn API. However, TestCafe seems easy to learn too, and provides a seamless testing experience with many APIs that can be used to test webpages.

However, in the end, it all boils down to preference. I will choose react-testing-library for unit and integration tests and TestCafe for end-to-end testing.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    Add to your HTML:

    <script src=""></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Murat Çatal I have over nine years of hands-on development experience as a fullstack developer, including technical lead positions. Currently overseeing development of enterprise applications, leading the frontend team.

6 Replies to “Comparing React testing libraries”

  1. How come Cypress gets four stars for browser support when it only works in Chrome? The entire post is flawed but this has really triggered me.

  2. Hi Milos,

    Thanks for your comment. But there is not any technical problem in document. Cypress supports Canary, Chrome, Chromium and Electron, so got 4 star while puppeteer only supports chromium and gets one star. In addition to that, cypress has roadmap ( for supporting firefox and ie11, also that feature will make it even stronger in near future so it deserves 4 star for me.

    Reference document

    Also, that document is created by prior experiences plus current documents of libraries. You may not be with same idea about authors’ personal comments (about that given stars…), but it is not right you to call whole work as useless and flawed.

  3. For end to end, @DXTestCafe should have been considered. Apart from awesome ES6 support, we can test on different browsers while Cypress and Puppeteer are Chrome browser constrained as of now.

  4. I am not sure it is! could you elaborate more on why you think the post is flawed?

    Also Cypress supports a wide range of browsers including Edge, Firefox and Electron (including unstable channels like dev and canary to test against future releases), the only reason that triggered you is actually not true.

    There is also a Safari Support request that is currently being evaluated:

Leave a Reply