How do i use a single render across multiple tests in React Testing Library.?
Asked Answered
B

3

6

Is it possible to maintain the same render() across multiple tests in using Jest and React Testing Library? I am building a Trivia App and have a single component that displays different questions as the user progresses through the quiz. In order to test the functionality of the choice buttons, the submit button, and checking that the right screens are displayed at the right time, I need to perform tests on the same rendered component at different stages of the quiz. For instance:

describe("Question Screen", () => {

    it("should render the first question when difficulty button is clicked", async () => {
        render(<TriviaBox/>);
        const btn = screen.getByRole("button", {name: /easy/i});
        fireEvent.click(btn);

        const heading = await screen.findByText("Question 1");
        expect(heading).toBeInTheDocument();
    });

    it("should display the next question when the current question is answered", async () => {
        render(<TriviaBox/>);
        const btn = screen.getByRole("button", {name: /easy/i});
        fireEvent.click(btn);

        const correctAnswer = await screen.findByRole("button", {name: /Nevada/i});
        const submit = await screen.findByRole("button", {name: /Submit/i});
        fireEvent.click(correctAnswer);
        fireEvent.click(submit);

        expect(wait screen.findByText("Question 2")).toBeInTheDocument();
        expect(wait screen.findByText("Which is the largest state?")).toBeInTheDocument();
        expect(wait screen.findAllByRole("radio")).toHaveLength(4);
        ...
    });
});

Is there a way to preserve the same render from the first test for use in the second test, rather than having to re-render the same component and step through the first question again in order to test the second question?

Barth answered 9/7, 2020 at 19:46 Comment(0)
I
12

Basically what you need is to disable auto cleanup because it unmounts React trees after each test. See docs: https://testing-library.com/docs/react-testing-library/setup/#skipping-auto-cleanup. But in this case you should care about calling cleanup manually to not compromise next tests.

Here is a small working example of how to do this with importing "@testing-library/react/dont-cleanup-after-each":

import "@testing-library/react/dont-cleanup-after-each";
import { render, screen, cleanup } from "@testing-library/react";

function TestComponent() {
    return (
        <div>
            <p>First element</p>
            <p>Second element</p>
        </div>
    );
}

describe("TestComponent", () => {
    afterAll(() => {
        cleanup();
    });
    it("should contain `First element` text", () => {
        render(<TestComponent />);
        screen.getByText("First element");
    });
    it("should contain `Second element` text", () => {
        screen.getByText("Second element");
    });
});
Ichthyosis answered 19/2, 2021 at 10:22 Comment(0)
U
0

One method to do this is to write a beforeAll function to initialise the render. That will initialise only once for all child tests.

describe("Question Screen", () => {

    beforeAll(() => {
        render(<TriviaBox/>);
    }) 

    it("should render the first question when difficulty button is clicked", async () => {
        const btn = screen.getByRole("button", {name: /easy/i});
        fireEvent.click(btn);

        const heading = await screen.findByText("Question 1");
        expect(heading).toBeInTheDocument();
    });
    ...
});

See the JEST docs https://jestjs.io/docs/en/setup-teardown#one-time-setup

Uranie answered 1/10, 2020 at 14:21 Comment(2)
Fair suggestion but doesn't work without extra steps. RTLib has auto cleanup after each test by default that needs to be disabled to make it work.Atrocity
@MaksimNesterenko, you could use beforeEach instead. It still renders before each test, but at least you save the repeated code.Notornis
F
0

As an alternative as suggested in skipping auto cleanup section of RTL setup doc, you can make below changes to your test/utility.ts file if you are using TypeScript:

import { render } from '@testing-library/react/pure';

import type { ReactElement } from 'react';

const customRender = (ui: ReactElement, options = {}) => {
    return render(ui, {
        // wrap provider(s) here if needed
        wrapper: ({ children }) => children,
        ...options,
    });
};

export * from '@testing-library/react/pure';
export { default as userEvent } from '@testing-library/user-event';
// override render export
export { customRender as render };

Notice that '@testing-library/react/pure' is used in import statements. After it always import { render, cleanup } from 'the/path/to/your/test/utility.ts' and strictly cleanup after all tests in suite.

import type { FC } from 'react';
import type { RenderResult } from 'path/to/your/test/utility';

const FactOfLife: FC = () => (
  <h1 data-testid="factOfLife">God is a programmer!</h1>
);

suite('FactOfLife component', () => {
    // below render is shared among testcases
    let factOfLife: RenderResult;

    beforeAll(() => {
        // initialize the render before all
        factOfLife = render(<FactOfLife />);
    });

    afterAll(() => {
        // very important step to avoid memory leak
        cleanup();
    })

    test('should render', ({ expect }) => {
        expect(factOfLife.getByTestId('factOfLife'))
            .toBeInTheDocument();
    });

    test('should contain fact of life', ({ expect }) => {
        expect(factOfLife.getByTestId('factOfLife')?.textContent)
            .toEqual('God is a programmer!');
    });
});

If you remove /pure from import statements in your test/utility.ts file then you get below error in 2nd testcase:

TestingLibraryElementError: Unable to find an element by: [data-testid="factOfLife"]

NOTE: I have deliberately used a different example than the one asked by the question poster.

Fatten answered 4/8 at 18:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.