Forem

Cover image for Testing react components
Rohan Bagchi
Rohan Bagchi

Posted on • Edited on • Originally published at rohanbagchi.vercel.app

3

Testing react components

As usual, opinions are personal and may contain some bias.

Over the years I found a common recurring pattern wherein React component tests are done by asserting on the internal behaviour of the function rather than the actual output.

Frontend unit tests must treat the test subject, our React Component as a black box just like how an end user might.
For example, when I am filtering apartments in Airbnb, I only care about the information I see on the screen and how it reacts to the filters I apply. At no point do I need to know if the underlying React component state has updated or if the text is an h2 or bold.

2 popular ways I know of, using which we can unit test React components:

  1. Enzyme
  2. React Testing Library

TL;DR I would use React Testing Library approach for writing meaningful unit tests.


What are we testing?

import "./styles.css";
import { useState } from "react";
import { get } from "axios";
export default function App() {
const [joke, setJoke] = useState(null);
const [error, setError] = useState(null);
const fetchJoke = async () => {
try {
const { data } = await get("https://api.icndb.com/jokes/random");
if (data.type === "success") {
setJoke(data?.value?.joke);
setError(null);
}
} catch (e) {
setError("Fetch failed. Please retry!");
}
};
const renderJoke = () => {
if (error) {
return <h3>{error}</h3>;
}
return <h3>{joke}</h3>;
};
return (
<div className="App">
<button onClick={fetchJoke}>Get a random joke</button>
{renderJoke()}
</div>
);
}
view raw App.js hosted with ❤ by GitHub

Here, how we render the joke is an implementation detail. The fact that the joke gets fetched and rendered on click of the button is the behavior.

For example, later on we could render the joke inside a <p/> tag. The behavior of the component would remain unchanged and as such we should not have to touch our tests for this change.

Enzyme test

import Enzyme from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import { shallow } from 'enzyme';
import App from './App';
import axios from 'axios';
Enzyme.configure({ adapter: new Adapter() });
const joke = 'Foo Bar!';
jest.mock('axios');
test('App', async () => {
const wrapper = shallow(<App />);
const P = new Promise(resolve => resolve({
data: {
type: "success",
value: {
joke
}
}
}));
axios.get = jest.fn().mockReturnValue(P);
wrapper
.find('button')
.simulate('click');
await P;
expect(wrapper.find('h3').text()).toEqual(joke)
})

Here, you will see we are extracting text content of the <h3/> element using wrapper.find('h3').text(). So based on our contrived example above, if we were to convert the <h3/> into a <p/>, our test will break. For a larger, more complex component, the changes will increase exponentially. This makes refactors hard.

React Testing Library Test

import { render, fireEvent, screen } from "@testing-library/react";
import { rest } from "msw";
import { setupServer } from "msw/node";
import App from "./App";
const joke = 'Foo Bar!';
const server = setupServer(
rest.get("https://api.icndb.com/jokes/random", (req, res, ctx) => {
return res(
ctx.json({
type: "success",
value: {
joke
}
})
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("App", async () => {
render(<App />);
fireEvent.click(screen.getByText("Get a random joke"));
expect(await screen.findByText(joke)).toBeDefined();
});
view raw App.rtl.test.js hosted with ❤ by GitHub

Here, we are only testing for the behavior. So, as long as the behavior stays same, an update in how we render the joke for this case makes no difference. For larger projects with lot more complexity, passing tests across refactors will give developers confidence and help them move fast.

Full repo here

Description

Created for showing a demo of testing the same React component using Enzyme and React Testing Library Where with Enzyme, it is more around the implementation details, with React Testing Library it is around the behavior.

Demo and fiddle

  1. Live demo
  2. Open repo in codesandbox

Link to dev.to blog post

https://rohanbagchi.vercel.app/testing-react-components/

How to run?

  1. npm i
  2. npm run test

This will trigger the tests and both will pass of course.

What are we testing?

import "./styles.css";
import { useState } from "react";
import { get } from "axios";
export default function App() {
  const [joke, setJoke] = useState(null);
  const [error, setError] = useState(null);

  const fetchJoke = async () => {
    try {
      const { data } = await get("https://api.icndb.com/jokes/random");
      if (data.type ===
Enter fullscreen mode Exit fullscreen mode

Thank you for reading this far. Let me know what you feel in comments.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay