The book guides you through the basics of React Testing Library, along with some testing recipes and cooking tips for testing on the way!!

We have created this handbook based on various documents and blogs that inspired us to create this beginner-friendly handbook.

Prerequisites

The book believes that you know the basics of React and that you have done some tests along the way using React testing library.

Flavors of Testing React Applications:

  “The more your tests resemble the way your software is used, the more confidence they can give you.” – Kent C. Dodds

 If you want to learn more about the Guiding Principles of React Testing Library check out this:

   https://testing-library.com/docs/guiding-principles   These are the common flavors of testing applications:
  1. Unit testing – verifies that the individual, isolated parts of the component are working as expected
  2. Integration testing – verifies that several units will be working together
  3. End to End / Functional testing – will be like a helper robot that will behave like a user to click around the app and verify the app functions correctly as expected
There are more flavors of testing react applications. The book concentrates more on Unit and Integration tests as they are fairly simple and easier to write using react testing library.

The book says you can use React testing library to cook these types of tests. So pick a recipe and start cooking your tests!

Let’s get into the recipes for using React Testing library.

Snapshots

Image that shows snapshot that references react testing library snapshot
The image that denotes a snapshot

Let’s get into the first testing recipe that’s taking a picture of your rendered component.

Snapshots are similar to pictures that you take out of your camera. It’s the output of your component that has been rendered in a DOM.

It helps us to identify if there is a change in UI. We have to keep a snapshot file inside the repository and compare the newly rendered DOM with it.

  • Snapshot tests are very useful whenever you want to make sure your UI does not change unexpectedly.
  • Snaps are the output of your component that will be rendered in the DOM. The test will fail each time there is a change in UI.
  • This kind of test notifies us of unexpected changes in the UI

When to take a snapshot?

Once we see the mighty power of snapshots, it is natural to write snapshots for all components, but that is not necessary.

They are huge and hard to look at, therefore we have to take snapshots only when necessary. Some of the places we feel that it is necessary for snapshots are below:

  • Snapshots should be taken only when the UI is completely rendered with all the data
  • There can be more than one snapshot when a component is rendered conditionally

When not to take a snapshot?

  • Snapshots should not be taken for UI that is still in transition due to an event or waiting for an asynchronous task to complete.
  • It should be taken only for a stable UI with mock data, the reason we say mock data is because real API data can change over time causing the tests to become flaky
  • A timer snapshot is most likely to fail every time.

Important note:  Use mock data that is similar to your real data and keep it up to date

Code snippet describing an improper snapshot
Code snippet describing an improper snapshot
 

Note: Always check the snapshot file. Some times snapshots may not contain fully rendered UI because of some asynchronous updates.

Code snippet describing proper snapshot
Code snippet describing a proper snapshot

Asynchronous Methods

Image Describing improper use of async functions
Image Describing improper use of async functions

The book says “Always wait for some time for the DOM to cook whenever you have fired an event”

React testing library provides several functions to deal with asynchronous code. These can be useful to wait for an element to appear or disappear in response to an event, user action, timeout, or Promise.

Always Make sure to use await before these methods

An async function without await is firing your stove and leaving. You never know what happens to it!

List of Async Methods in React Testing Library

  • findBy (all findBy queries are asynchronous)
  • waitFor
  • waitForElementToBeRemoved

Eg : 

await  waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))

QueryBy

  • Use the queryBy whenever you need to check non-existence because queryBy does not throw like the other testing library methods.

For more information on the available methods check out:  https://testing-library.com/docs/react-testing-library/cheatsheet/

expect(queryByText(‘SignOut’)).not.tobeInTheDocument();

If you want to check something that is not available initially prefer to use findBy. Since findBy is asynchronous and it reduces the lines of code by avoiding waitFor.

const submitButton = await screen.findByText(‘Login’)

 

Code snippet describing the proper use of waits and matchers
Code snippet describing the proper use of waits and matchers
 

 

Don’t mock or spyOn react’s inbuild hooks/lifecycle methods

The book says “Always taste the food, not the stove”.

While testing, we have to focus on testing the output and functionality of the component as observed by the user.

Some wrong Eg: mocking on useState() and checking whether it’s called, is unnecessary. Because we can expect the reflection in UI instead of mocking useState.

In order to test the updation of a state or triggering of any lifecycle method/hook.  We have to always expect the UI to be updated, as seen by a user!

Note: we can expect the functions / events inside the component lifecycle methods but not the component lifecycle method itself

Proper Expects

The book says “You should not expect spice in an ice cream”.

So expect properly to make sure your tests cover your code.

  • Always Expect with values or parameters, like using toHaveBeenCalledWith() instead of toHaveBeenCalled()

Eg:

Code snippet describing proper expects
Code snippet describing proper expects

  • If there is more than one function call in a block expect all the functions to be called with the right parameters.

Eg:

Code snippet describing improper use of function call counts
Code snippet describing improper use of function call counts

  • Check the number of times the function is called within the test case
code snippet describing use of proper call counts
code snippet describing the usage of proper call counts

Checking for Multiple items in the List

A Running Mouse image that refers to a loop
A Running Mouse image that refers to a loop
  • Use loops to avoid writing excessive tests on a list

  • Even though looping covers all your code in some cases if an item is modified/ removed it never fails!! Therefore never letting you know that something has changed

Tip: Create a separate constant list and use it to loop so that even if somethings changed the test fails!!

code snippet describing use of looping in tests
code snippet describing the usage of looping in tests

Clearing Mocks and mock states Properly

  • Improper cleaning of mocks and mock states means you are “To jump out of the frying pan into the fire “. If you do not properly clear mocks and mock states the tests do not work well!!

Tip: You can use .only while writing individual tests so that the test case is not affected by above tests.

Using jest.clearAllMocks():

Resets all the mocks usage data, not their implementation.

Code snippet examples for mock clearing
Code snippet examples for mock clearing

Since the mock data is not cleared the mock function is called 2 times.

Using jest.resetAllMocks()

It will replace the mock function with a new jest.fn()

code snipet example for resetAllMocks
code snippet example for resetAllMocks

Using jest.restoreAllMocks()

restoreAllMocks will clear the mock states and reset the function to its original implementation

code snippet example for restoreAllMocks
code snippet example for restoreAllMocks

Mock states may get carried over to the next tests

Use clearAllMocks to avoid these behaviors

afterEach(() => {
jest.clearAllMocks();
});

Having multiple assertions in waitFor

The book says “Too many cooks spoil the broth”.

  • waitFor separately works best, but the waitFor does not wait for each individual expect block.
Code snippet showing multipe assertions in waitFor
Code snippet showing multiple assertions in waitFor

Don’t have fireEvents inside waitFor

Tip: Don’t have any synchronous code inside waitFor

waitFor method is a powerful asynchronous function that helps us to make an assertion after a non-deterministic amount of time.

The way waitFor works is that polls until the callback we pass stops throwing an error. So if we were to make side-effects within the callback, those side-effects could trigger a non-deterministic number of times. There is a default timeout also for waitFor.

Instead of destructuring from render, use screen

The book says “Keep knives close to the vegetables”. You can use the screen to avoid unnecessary destructing of methods

Code snippet that shows use of screen from react testing library
The code snippet that shows the use of screen from react testing library

Chaining mocks

When mocking multiple modules you can chain them:

jest
.mock('./time', () => jest.fn())
.mock('./space', () => jest.fn());

Try to use getByText instead of data-testids for unique texts

While using data-testids give you a stable reference to test the component. It does not verify the text message in the component. So it’s better to use the text in your component when it’s unique as it helps you verify the text along the way reducing your expect blocks. You can use findByText also to wait for async updates.  
code snippet example that uses text to verify component
code snippet example that uses text to verify component

Mocking:

  • Try to use automatic mocks i.e jest.mock(‘filename’);

  • If you want to mock a specific function in a file alone it’s better to use spyOn as it will only mock the function it’s spying on

  • Don’t use both jest.mock and spyOn on the same files.

Tip : Avoid using default exports and named exports in the same files, as they will make the mocking harder.

Methods of mocking:

Mocking a named export

If you wanted to mock an imported named function, say getTime:

  • Automatic mocks can be used

Note : this will mock all the other named functions as jest.fn()

code snippet for modue factory and automatic mocks
code snippet for module factory and automatic mocks

Mocking only the named import (and leaving other imports unmocked) – Instead, you can use a spyOn

When there are multiple functions in a module, and you only want to mock one, you can use requireActual:

code snippet for jest.requireActual
code snippet for jest.requireActual

 Mocking a default export:
code snippet for mock method
code snippet for mock method

Mocking default and named exports

When you want to mock default and named imports, you’ll need to remember to use __esModule: true.

code snippet for mocking both default and named export
code snippet for mocking both default and named export

Mocking Functions inside a module that use another function in the same module

code snippet with two functions inside same file
code snippet with two functions inside same file
failed code snippet for module mocking inside same file
failed code snippet for module mocking inside same file

Mocks will only work for exported functions. Since the greet function uses the function directly from the file it will not be mocked.

You can also check this blog Jest — How To Mock a Function Call Inside a Module which provided us with the information to write this article!!

Solution 1 — Splitting The Module Into Different Files


If you move b to its own file:

solution code for module mocking inside same file
solution code for module mocking inside same file

Solution 2 — Calling The Mocked Function Using Exports

Adding exports before calling the function. This makes the function use the exported reference of the function

solution code for module mocking inside same file
solution code for module mocking inside the same file

ACT errors:

Error Example Snippet
Error Example Snippet

These errors may mock you over!

Warning: An update to YourComponent inside a test was not wrapped in the act(…).

When testing, code that causes React state updates should be wrapped into the act(…):

act(() => {
/* fire events that update state */
});
/* assert on the output */

This ensures that you’re testing the behavior the user would see in the browser. Learn more at – https://fb.me/react-wrap-tests-with-act

Looking into act errors are essential as they show you some unnecessary side effect that you might have not expected

Note : add await waitFor( ) for the next expect()

Eg: 

await waitFor(() => expect(screen.queryByText('saved')).toBeInTheDocument())

Testing File Uploads:

 For testing file uploads, we can use the fireEvent change method and update the target with files by creating a mock file.

Eg:

fireEvent.change(getByTestId('upload-front-id'), {
target: {
files: [new File(['(⌐□_□)'], 'someimage.png', { type: 'image/png' })],
},
});

Testing Timer Related Components:

Animated image that describes the complexity of testing timers
An animated image that describes the complexity of testing timers

Working with timer-related components can be similar to defusing a bomb. If you change something all the tests break!!

Always be cautious with timer-related tests, since it causes tests to become flaky. They also could affect a lot of the performance of your tests.

TestFile.js

While you can call jest.useFakeTimers() or jest.useRealTimers() from anywhere (top level, inside it block, etc.).

It is a global operation and will affect other tests within the same file. Additionally, you need to call jest.useFakeTimers() to reset internal counters before each test.

If you plan not to use fake timers in all your tests, you will want to clean up manually, as otherwise, the faked timers will leak across tests:

Code snippet example for fake Timers
Code snippet example for fake Timers

useFakeTimers() is leaky and should be cleaned up manually otherwise it will affect all the timeouts

jest.runOnlyPendingTimers(); – can be used to Fast forward and exhaust only currently pending timers (but not any new timers that get created during that process)

setupTests.js

If your app uses a browser API that you need to mock in your tests or if you need a global setup before running your tests, add a src/setupTests.js to your project. It will be automatically executed before running your tests.

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
global.localStorage = localStorageMock;

Finally, the book recommends that you write your tests in a way that they resemble the user behavior and fail more often when there are changes. Having test cases that are more volatile always keeps us in check. Keep in mind that it should be decoupled and fail only when there is a change in the module you are working on, not in any other modules.

Now that you have learned basic cooking skills, start cooking your tests!

While cooking your way, you may learn new recipes and tips, feel free to share those below.

References

https://testing-library.com/docs/guiding-principles

https://www.benmvp.com/blog/react-testing-library-best-practices/

https://www.emgoto.com/mocking-with-jest

https://stackoverflow.com/questions/56722139/when-testing-code-that-causes-react-state-updates-should-be-wrapped-into-act

https://medium.com/welldone-software/jest-how-to-mock-a-function-call-inside-a-module-21c05c57a39f

Authors

Leave a Reply

Login with