Testing
Shallow rendering
React provides us with a nice add-on called the Shallow Renderer. This renderer will render a React component one level deep. Let's explore what that means with a simple <Button>
component.
This component renders a <button>
element containing a checkmark icon and some text:
button.tsx
button.tsx
Note: This is a stateless (aka "dumb") component
It might be used in another component like this:
Homepage.tsx
Homepage.tsx
Note: This is a stateful (or "smart") component
When rendered normally with the standard ReactDOMClient.createRoot().render()
function, this will be the HTML output (Comments added in parallel to compare structures in HTML from JSX source):
Conversely, when rendered with the shallow renderer, we'll get a String containing this "HTML":
If we test our Button
with the normal renderer and there's a problem with the CheckmarkIcon
, then the test for the Button
will fail as well. This makes it harder to find the culprit. Using the shallow renderer, we isolate the problem's cause since we don't render any components other than the one we're testing!
Note that when using the shallow renderer, all assertions have to be done manually, and you cannot test anything that needs the DOM.
react-testing-library
To write more maintainable tests that more closely resemble the way our component is used in real life, we have included react-testing-library. This library renders our component within a simulated DOM and provides utilities for querying it.
Let's give it a go with our <Button />
component, shall we? First, let's check that it renders our component with its children, if any, and second, that it handles clicks.
This is our test setup:
button.test.tsx
button.test.tsx
Snapshot testing
Let's start by ensuring that it renders our component and no changes happened to that component since the last time it was successfully tested.
We will do so by rendering it and creating a snapshot which can be compared with a previously committed snapshot. If no snapshot exists, a new one is created.
For this, we first call render
. This will render our <Button />
component into a container, by default a <div>
, which is appended to document.body
. We then create a snapshot and expect
that this snapshot is the same as the existing snapshot, taken in a previous run of this test, and committed to the repository.
render
returns an object that has a property container
and yes, this is the container our <Button />
component has been rendered in.
As this is rendered within a normal DOM we can query our component with container.firstChild
. This will be our subject for a snapshot. Snapshots are placed in the __snapshots__
folder within our __tests__
folder. Make sure you commit these snapshots to your repository.
Great! So, now if anyone makes any change to our <Button />
component the test will fail and we will get notified about the change.
Behavior testing
Onwards to our last and most advanced test: checking that our <Button />
handles clicks correctly.
We'll use a mock function for this. A mock function is a function that keeps track of if, how often, and with what arguments it has been called. We pass this function as the onClick
handler to our component, simulate a click, and, lastly, check that our mock function was called:
button.test.tsx
button.test.tsx
Our finished test file looks like this:
button.test.tsx
button.test.tsx
And that's how you unit-test your components and make sure they work correctly!
Be sure to have a look at our example application. It deliberately shows some variations of test implementations with react-testing-library
.
For more robust user interaction tests, see @testing-library/user-event.
fireEvent
dispatches DOM events, butuser-event
should be used to simulate full interactions, which may fire multiple events and do additional checks along the way.
Last updated