Eagle Black Ltd

Jest with React and Typescript

This cheat sheet covers the basics of testing React components and provides examples of common scenarios you might encounter while testing your components.

Mocking dependencies

In this example, we are mocking the myModule module using Jest's jest.mock function. We replace the implementation of the getData function with a mock function that returns a resolved promise with { data: 'mocked data' }. This allows us to control the return value of the getData function in our tests without making actual network requests to https://api.example.com/data.

In the test case, we can then use the mocked version of myModule to test that it returns the expected data.

// myModule.js export const getData = () => { return fetch("https://api.example.com/data").then((response) => response.json() ); }; // myModule.test.js import * as myModule from "./myModule"; jest.mock("./myModule", () => { return { getData: jest.fn(() => Promise.resolve({ data: "mocked data" })), }; }); describe("myModule", () => { it("returns mocked data", async () => { const data = await myModule.getData(); expect(data).toEqual({ data: "mocked data" }); }); });

Mocking default modules

import myModule from "./myService"; jest.mock("./myService", () => ({ __esModule: true, default: jest.fn(() => ({ myResponse: "test" })), })); it("should call myModule", () => { expect(myModule).toHaveBeenCalledWith("1"); });

Varying mocked response per test (plus mocking async functions)

jest.mock("./myService", () => ({ __esModule: true, default: jest.fn(async (param) => { return param.success ? Promise.resolve({ status: 200, data: { mockResponseData: "Test" } }) : Promise.reject(new Error("500 error")); }), }));

or

import axios from "axios" jest.mock("axios", () => ({ get: jest.fn(), })) beforeAll(() => { jest.spyOn(axios, "get").mockImplementationOnce(async () => { return Promise.resolve({status: 200, data: {items: []}}) }) await getProducts(mockRequest as any, mockResponse) }) afterAll(() => { jest.clearAllMocks() })

Mocking react components

Same as module mocking but you can return an element

jest.mock("./components/facets", () => ({ __esModule: true, default: () => <div>TEST FACETS</div>, }));

It can be used with parameters

jest.mock("./components/container", () => ({ __esModule: true, default: ({ children, textAlignment }) => ( <div> TEST CONTAINER {textAlignment} {children} </div> ), }));

Adding custom functions to mocked react components

jest.mock("./components/button", () => { const mockApp = () => { return <div />; }; mockApp.getServerSideProps = jest.fn(async () => ({ mockAppProps: "EXPECTED SSR PROPS", })); return { __esModule: true, default: mockApp, }; });

Asserting mocked components are called with parameters

jest.mock("../filters", () => ({ __esModule: true, default: jest.fn(() => <div>TEST FILTERS</div>), })); describe("Facets - Feats: ", () => { it("should match the snapshot ", () => { const { asFragment } = render( <FacetsFeats filters={mockFilters} borderColour="RED" /> ); expect(asFragment()).toMatchSnapshot(); expect(Filters).toHaveBeenCalledWith( { filters: mockFilters, }, {} ); }); });

Partial match assertion object

In this example, expect.objectContaining is used to check that the actual object contains properties with specific values. The assertion passes if the actual object contains properties name with value 'John' and age with value 30, regardless of any other properties that may be present in the object.

const actual = { name: "John", age: 30, city: "New York", }; expect(actual).toEqual( expect.objectContaining({ name: "John", age: 30, }) );

Repeat test for multiple test cases

In this example, it.each is used to run a set of tests for the add function, where the test inputs and expected outputs are defined in a table. Each row in the table creates a separate test case. The function passed to it.each is run for each test case, and the current test case's values are passed as an object to the function.

const add = (a: number, b: number) => a + b; describe("add", () => { it.each([ { a: 1, b: 1, expected: 2 }, { a: 2, b: 3, expected: 5 }, { a: 3, b: 4, expected: 7 }, ])("returns $expected when $a is added to $b", ({ a, b, expected }) => { expect(add(a, b)).toBe(expected); }); });

Prefer userEvent over fireEvent

RTL's documentation does not make this clear, but there are two ways to replicate user interactions, with fireEvent and with userEvent. In fact, their examples page show both: https://testing-library.com/docs/react-testing-library/example-intro

By default, you should always prefer to use userEvent. userEvent is an abstraction that wraps the lower level API of fireEvent. Read more about it here https://testing-library.com/docs/user-event/intro

"userEvent" provides a simpler and more intuitive API for triggering user interactions, making it easier to write tests that reflect real-world use cases. It provides utility functions that are specifically designed to test accessibility-related scenarios, such as triggering keyboard events or inputting text into form fields. Its functions are designed to wait for the necessary state changes and rendering to occur before triggering the next interaction, making tests more reliable and less prone to race conditions.