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);
})
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'.
})
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!
Top comments (10)
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
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
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 aloaded
boolean property tofalse
and if it renders the actual image whenloaded
istrue
. The way you'll use that would look something like this:And then you can test it like this:
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'simg
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.
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.And all of these events are native, none are custom. π
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).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.
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.
In the first sniplet you have small typos. There isn't altText variable nor getByAltText function, so the test will fail
Corrected it. Thanks for pointing it out!