React testing overview
Khanh Nguyen • 17 February 2023 •
React testing fundamentals
React help simplify building a web application by establish a contract with developers that render output of React components is entirely derived from data provided.
The only way to change rendered output is by chaging the data provided.
That was great news for those who wish to write tests for React applications.
Create testable components
Ideally, a React component should be design in a way that's testable, which should ensure extracting application logic outside of components.
When testing, we focus mostly on#
- Props
- Contain data provided to render outcome for a component
- Supply callback functions to pass data back out of the component.
- Testing component interactions by asserting that callback functions were called correctly with the expected arguments.
- DOM: Tesing by verify DOM elements tree return by render function.
Barries to Testability#
- Global variables: Components use whichever data not supplied via its props causing tests for those components to cover controlling over the global variables. For the test to be meaningful, we would have to be confident that the global variables act the same way in the testing and application scenarios. Which is difficult, if not possible.
- Context: Slightly less global variables, therefore, inherit the same problem.
- UseEffect: Break the diagram of React component integrating only by its props. Hence, it break our ability to test components by verifying the output render, and assert that props functions are called.
- UseState: Less of a problem compare to UseEffect. If a component has state, then the rendered output is no longer solely dependent upon the props supplied.
Testing component rendering
Required tools#
- Javascript runtime: NodeJs(and alternative)
- Javascript test runer: To discover the tests, execute and report. Usually
Jest
orCypress
- Test assertion library: To provide the methods used to make test fail when the behavior doesn't match expectation.
Usually,
Jest
also cover this part. - DOM and HTML environment: To host components. This can be a real web browser or simulated environment.
Usually, we use
JS DOM
- UI testing library: To help find elements in rendered output, and to help trigger events on those elements. Usually, we use React Testing Library
React Testing Library query functions basic and examples#
Query Functions#
What are we testing#
Before we jump to actual tests, we should extract as much business logic and component states as possible.
They'll be useful for testing interaction tests and rendering tests later.
Beside that, Jest mock capability is useful for complex scenarios.
Rendering#
Testing Asynchronous Rendering#
There's primary 2 methods to achieve this expected behavior
- Using
find
query, default timeout is 1s. - Using
waitFor
will take a lambda function and calls it reapeatedly until it doesn't throw an exception and the test passes or waiting period expired and the test fail.
Be careful with them both, and remember that they return a promise.
Snapshot testing#
Best suit for some specific cases that we're sure the rendered component should not be change over time. Jest provide 2 common function to achieve this:
toMatchSnapshot
: When the test first run, Jest will capture a particular result and append it into a separate file with corresponding name.toMatchInlineSnapshot
: When the test first run, Jest will capture a particular result append it into source code correspondingly with test cases.
When the test execute later, it'll compare with the existing value to determine if the test should be fail or not.
This kind of tests are usually used with react-test-renderer
in order to serialize result that can be store as string.
User Interaction#
userEvent#
Tried to resemble how user interact with your application, simulate what happen in a browser.
- Thus, a click
user-event
is translated into the series of events(mouse over, mouse move, mouse down, focus, mouse up and click) - Further,
user-event
adds visibility and interactability tests to prevent the test clicking a button that is hidden or entering text into a disabled textbox - Need to call
setup()
before rendering - Interaction methods are asynchronous
fireEvent#
Simply allows us to trigger DOM events on DOM elements. It is not an accurate representation of what happens when a human uses a browser. Therefore, it's simple.
- Trigger any event on any element
- Be aware of limitation
- Doesn't interact exactly as a user would
- The sequence of events leave many out. For example
- Focus is not moved correctly
- Visibility, interactability are not considered.
- Interaction methods are synchronous