Testing React application with testing-library

Author : JaNakh Pon , January 22, 2022

Tags

Intro to Testing

Testing is a line-by-line review of how your code will execute. A suite of tests for an application comprises various bit of code to verify whether an application is executing successfully and without error. Testing also comes in handy when updates are made to code. After updating a piece of code, you can run a test to ensure that the update does not break functionality already in the application.


UNIT TEST

Only individual units or components of the sotware/app are tested in Unit Testing. A unit might be an individual function, method, procedure, module, or object. A unit test isolates a section of code and verifies its correctness, in order to validate that each unit of the software’s code performs as expected.

In unit testing, individual procedures or functions are tested to guarantee that they are operating properly, and all components are tested individually.

SNAPSHOT TEST

A snapshot test makes sure that the user interface (UI) of a web application does not change unexpectedly. For example, it captures the current code/content of the component at the moment and compare it with future changes of the component to make sure there ain't any unexpected changes in the component.

Setup

"@testing-library/react" is included in create-react-app by default so, we will just use CRA.

Let's start by creating __tests__ folder under /src/ and create App.test.js as a test suite/file for App.js.

When we wrie test functions in test suite, we should think about testing "how user will use it and what should be ready/working for user interactions with the app".

So, for this todo app, we will start by writing tests for two main components: "textbox" and "tasklist":

App.test.js
test("textarea should be in DOM", () => {
    render(<App />);
    const textboxElement = screen.getByRole("textbox");
    expect(textboxElement).toBeInTheDocument();
});

test("Task list should be in DOM", () => {
    render(<App />);
    const taskListElement = screen.getByRole("list");
    expect(taskListElement).toBeInTheDocument();
});

test("Task list should have 4 list items", () => {
    render(<App />);
    const taskListElement = screen.getByRole("list");
    const { getAllByRole } = within(taskListElement);
    const items = getAllByRole("listitem");
    // list should have 4 listItems by default
    expect(items.length).toBe(4);
});

In our todo app, when user type something in textbox and submit it, a new listItem should be popped up in list component and we want to make sure that function is working properly and that new listItem is in the DOM:

App.test.js
test("Task list length should be increased after textarea form submit", () => {
    render(<App />);
    const textboxElement = screen.getByRole("textbox");
    fireEvent.focus(textboxElement);
    fireEvent.change(textboxElement, {
        target: { value: "Hello World" },
    });
    fireEvent.keyDown(textboxElement, {
        key: "Enter",
        code: "Enter",
        keyCode: 13,
        charCode: 13
    });
    const taskListElement = screen.getByRole("list");
    const { getAllByRole } = within(taskListElement);
    const items = getAllByRole("listitem");
    expect(items.length).toBe(5);
    const addedItemHeading = screen.getByRole('heading', { name: /hello world/i });
    expect(addedItemHeading).toBeInTheDocument();
});

In listItem of list, we have "delete icon" and when the user click it, that listItem is removed from the DOM:

App.test.js
test("Task list length should be decreased after clicked delete button", () => {
    render(<App />);
    const buttonElement = screen.getByTestId('itemButton0');
    fireEvent.click(buttonElement);
    const taskListElement = screen.getByRole("list");
    const { getAllByRole } = within(taskListElement);
    const items = getAllByRole("listitem");
    expect(items.length).toBe(4);
});

And finally, we want to have a snapshot of the whole component to avoid unexpected changes in UI in the feature updates:

App.test.js
test("Container with 4 items snapshot", () => {
    const { container } = render(<App />);
    expect(container).toMatchSnapshot();
});

Now, if we run npm run test || npm t in terminal, we should see the following results:

App test suite

However, if we change the default tasks state in our App.js or we add a new element to App.js and run the test, it will fail because the code/state stored in snapshot has changed and also the other tests will fail because default length of listitem in our test is 4.

So, if we want to have the changes as the update, we will have to change the targeted values in our tests and update the snapshot by entering/pressing u:


References

A Practical Guide To Testing React Applications With Jest

Testing Lists Items With React Testing Library

Animation with Framer Motion

Resources

Source Code.