Testing React hooks and Components
Monday, July 8, 2024 - 7 min read
In our previous blog post, we discussed getting started with unit testing in JavaScript/TypeScript. check it if you missed it here Now that we have a testing environment set up, let's explore how to test React hooks and components.
Testing React Components
Imagine a simple Button
component that accepts two props: children
and onClick
.
We want to ensure this button renders correctly and the onClick function gets called when clicked.
Testing Rendering
describe
is used here to define the<Button/>
test suite- a test suite is a group of related tests
it
is used to define a test- we render the
<Button/>
and expect it to be in the document
the test should run successfully ! congrats the button component renders properly.
now let's test the onClick
Testing the click functionality
in order to test a user behavior, we have to simulate the user action. luckily @testing-library/user-event
provides
exactly this
- we setup the user via
userEvent.setup()
- we mock an onClick function using
vi.fn()
. why ? mocking the function allows us to test it and check whether it got called or not and a bunch of other tests - we render the
<Button/>
and pass the onClick handler to it - we get the
<Button/>
via the screen and click it (note that user events in@testing-library/user-event
are async calls so we must wait it) - then we expect the mocked
onClicked
to have been called
the test should run successfully!
Skip redundant tests:
We can skip checking things that are implied by other tests.
In our example, we had separate tests for rendering and button click functionality.
However, testing interactions like clicks inherently verifies that the component is rendered correctly.
When a button is clicked, it must be present in the DOM to be interactive.
We can now remove the render test and be confident that it's tested in the click functionality.
Testing React Hooks
Imagine a simple useCounter
hook that manages a counter state and provides functions to increment and decrement it:
We want to ensure that
- the hook works correctly and that count value is initialized with zero
- the value increments correctly
- the value decrements correctly
let's test that it initializes correctly and that count value is initialized with zero
- we use the
renderHook
from@testing-library/react
to render the hook and test it in isolation without a component. renderHook
returns an object containing the propertyresult
to test the result of the render.result
contains an object with the propertycurrent
which we use to test the current value in the render (note that it's preferred to testresult.current
immediately not assigning it to another variable to avoid stale closures)
Testing that it increments correctly
we must use the act
method to wrap actions that cause a state update/s when using react-testing-library
.
Testing that it decrements correctly
the tests should run successfully!
Testing React Hooks with browser events
Imagine a hook useIsOnline
hook that detects if the browser is online or offline based on the browser events online
& offline
and also takes an optional props onOnline
& onOffile
that run when online
& offline
happens respectively.
We want to ensure that the hook works correctly so we want to test
- it initializes with the state set to
true
(when first running the js in the browser, it must be online) - it should update the state to
false
whenoffline
and call theonOffline
prop - it should update the state to
true
whenonline
and call theonOffline
prop - it should remove the two event listeners when unmounted (to avoid leaky subscriptions)
so let's test that it initializes with true
now let's test the offline functionality
- we dispatch the
offline
event manually to test it
now let's test the online functionality
now let's test that it removes the event listeners
- we spy on the
removeEventListener
method viavi.spyOn
and mock it's implementation viavi.fn
to test it renderHook
returns an object containing the propertyunmount
which we can use to test the un-mouting of the hooks- we call the
unmount
andexpect
theremoveEventListener
to be called twice one for theonline
and one for theoffline
event-listeners
you can now test most of the components and hooks
Congrats !