DEV Community

loading...
Cover image for Understanding React Testing Library

Understanding React Testing Library

Dhaiwat Pandya
I share what I know about React
・3 min read

Who doesn't love React Testing Library? If you write tests for React, you probably use it a lot. People even like to call it the successor of Enzyme, but React Testing Library and Enzyme are nothing alike. While React Testing Library already gives you superpowers — understanding its principles will help you write much better tests for your React UIs. I promise you will gain something out of this short read.

The idea & the principles

The user lives in the heart of the principles of React Testing Library. Everything that the library does, revolves around the user.

Good UI tests give you confidence that your components work for your users. They verify that your components look right and behave the right way. How you implement it behind the scenes is not important for the user. This is why good UI tests should never rely on the implementation details of a UI component.

If your tests rely on how a given component is implemented, those test cases will break when you re-factor your codebase. This makes your tests extremely hard to maintain and will slow you down. You don't want that.

Remember — you want your UI tests to verify the end result, not the internal implementation. This is the pivotal idea behind React Testing Library.

The more your tests resemble the way your software is used, the more confidence they can give you.

testing-library.com

Enzyme gives you access to the component's state, props, children, etc. React Testing Library doesn't do that. It gives you the DOM instead because that's what your users will have. If your tests are good enough, you will never have to explicitly access the component's state or props to make any assertions. Just use the DOM.

Passing a backgroundColor prop to a <Button />? Render the button to the DOM and verify that the rendered button's background color. Don't access the props of the component and make an assertion. Make a visual assertion.

it('should apply the background color properly', async () => {
    const bgColor = '#ccc222';
    const text= 'hi';
    const { getByText} = render(<Button backgroundColor={bgColor} text={text} />);
    const button = getByText(text);

    expect(button.style.backgroundColor).toEqual(bgColor);
})
Enter fullscreen mode Exit fullscreen mode

Want to test the loading state of a component? Make an assertion on the way the component looks when it is loading. Don't verify whether the loading state of the component is true.

it('should render correctly while loading', async () => {
    const src= '#';
    const altText = 'hi';
    const { getByAltText } = render(<Image src={src} alt={altText} />);
    const image = getByAltText(altText);
    expect(image.src).toEqual(loadingSrc);
    // Note: you need to write fireEvent.load(image) in order to complete loading the image.
    // Since we have not done that, the image is still 'loading'.
})
Enter fullscreen mode Exit fullscreen mode

The benefits

React Testing Library is inspired by its love for great user experience. If you are writing good tests using React Testing Library, you can be assured that the experience you ship to your users will be what you wanted. It gives you that much-needed confidence when pushing to production and let me tell you, it feels good. Anything that relieves you of some stress is a blessing and React Testing Library is definitely one.

What about the developer experience? Well, React Testing Library excels at that, too. The syntax is extremely intuitive. You don't have to know any intricacies in order to get up and running. The querying methods like getByText, getByAltText, etc allow developers to query the DOM just like a real end-user. This is so important.

Another massive benefit this library offers to developers is that as long as you only re-factor your component's implementation (not functionality), your tests will not break. I might be repeating myself here, but this will save you a lot of time & headaches. And you will absolutely love it when you refactor the code and nothing breaks. Trust me.

Oh, and the documentation is everything a developer would want from a library. It's perfect.

Conclusion

All in all, React Testing Library helps you ship UIs which are optimized for your end-users. This is something no developer/team would say no to.

I listed a lot of pros of using the library here but if you think there are any cons, comment down below. Let's discuss!

If you gained anything from this article, please follow me here on DEV & on Twitter. I try to balance my Twitter content between knowledge & shitposts. I can promise you will not be disappointed!

References & resources

Discussion (10)

Collapse
lukeshiru profile image
LUKESHIRU

The idea with React Testing Library is to test what the "user/consumer" of your components will see, so doing something like setting a color property and then checking if the style property is set is not ideal, you could use a snapshot maybe to check if the output HTML is the expected one. Same with events, you should check if the event handlers are called, but not if the internal state of the component changes, because the user/consumer doesn't actually care about the internal state.

Trust me, your test will become far simpler and far more useful than before :D

Collapse
dhaiwat10 profile image
Dhaiwat Pandya Author

Hi, I can understand the point you are trying to make with snapshots. But am I checking the internal state of the component in the post? I don't think I am. I don't think you can, actually. I am getting the component from the actual DOM and making some assertions (all about its HTML attributes, no internal state). Isn't that the same as comparing the rendered component to a pre-defined expected HTML? Would love to hear your thoughts. Thanks

Collapse
lukeshiru profile image
LUKESHIRU

My suggestion was mainly because the second example expects the component to have an internal loading state and we need to fire a load event which feels kinda weird. Ideally state should come from the parent in the form of props, and you can test for example if the component renders as expected when setting a loaded boolean property to false and if it renders the actual image when loaded is true. The way you'll use that would look something like this:

const [loaded, setLoaded] = useState(false);

return <Image src="example.png" loaded={loaded} onLoad={() => setLoaded(true)} />
Enter fullscreen mode Exit fullscreen mode

And then you can test it like this:

it('should render correctly while loading', async () => {
    const { asFragment } = render(<Image src="test.png" />);
    expect(asFragment()).toMatchSnapshot();
});

it('should render correctly when is loaded', async () => {
    const { asFragment } = render(<Image src="test.png" loaded />);
    expect(asFragment()).toMatchSnapshot();
});
Enter fullscreen mode Exit fullscreen mode

If you have your own mechanism to load the image and dispatch the onLoad event then you need to test that as well, but if you're using the browser's img element, you don't need to test it.

Don't get me wrong, the article is great, I just wanted to add to it by mentioning that ideally you shouldn't test stuff like internal state, or native behaviors, when you can just check if the output is the one you expect for different combinations of your custom props, and that the expected custom events are called.

Thread Thread
dhaiwat10 profile image
Dhaiwat Pandya Author

Thanks for the reply!

I think we are on different pages here. I don't have any loading state in my component like you mentioned. By default, Testing Library doesn't actually load your image. It doesn't fire the 'onLoad' event. (That's what we are doing with fireEvent.load(), not updating any internal state.) Which is why you can test if the image is correctly displaying a loader using the code I mentioned.

Thread Thread
dhaiwat10 profile image
Dhaiwat Pandya Author

And all of these events are native, none are custom. 😁

Thread Thread
lukeshiru profile image
LUKESHIRU

That's what I'm saying 😅 ... why would you test if the native img onLoad event works? Or if it changes something? If you do that, it seems like the component has a side effect when the image is done loading (which you'll emulate from RTL with fireEvent.load). Ideally the component shouldn't do that by itself, but actually receive it from the parent (which is easier to test, because you don't need that fireEvent at all).

Collapse
fewwy profile image
Fewwy

Why should I use this library instead of Jest and Enzyme?
I don't need to check button's color because the snapshots will check that automatically.
I'm not hating on the testing library, I'm trying to understand why should I use it.

Collapse
dhaiwat10 profile image
Dhaiwat Pandya Author

This library does not replace Jest. It's not a test runner.

And like I mentioned in the article, React Testing Library forces you to test your components from your user's perspective. I know you can do that with Snapshots, but the examples I put in my article are very very basic. The library allows you to do much more than these things.

With Enzyme, it’s common to find elements in the page by their class, which is not meaningful because users do not see those in the UI. With react-testing-library, you search directly by the actual text that the user sees without the overhead work of finding the element that contains that text.

Collapse
amn3s1a2018 profile image
Amn3s1a2018

In the first sniplet you have small typos. There isn't altText variable nor getByAltText function, so the test will fail

Collapse
dhaiwat10 profile image
Dhaiwat Pandya Author

Corrected it. Thanks for pointing it out!