loading likes...08 April, 2023 105 total views

Let's Write Tests for React Application #2

Let's Write Tests for React Application #2

It was a long-awaited article series. Actually, I also wanted to do a few things to improve myself on testing. This article was an excuse for me to tell you. Well, as you know, if there is something you want to learn, you need to tell it to someone. You need to learn it better... That's why I'm here :)

Before starting the article, let me quickly pass the backlink. For those who haven't read the first article of the series, let's take it here. Let's Write Tests for React Application #1

We will get straight to the topic without going into what is testing, what is unit testing, what is integration testing. I developed a project to make it more memorable and shared it publicly on github. This way, you can examine the project and understand the subject of testing at least to some extent.

Let's Review the Project

Actually, I tried to keep the project simple. This way, we can better focus on the topic we need to focus on. The project is a simple guestbook application. It is a project that works completely locally, has no API connection, and consists of a few components. You can review the live version of the project at Guestbook.

If you want to access the source codes on Github, you can reach them at github.com/berat/guestbook. I also took a screen recording while developing the project and shared it on YouTube.

Let's Start with Unit Tests

Actually, I didn't know exactly which situations I could explore. But the ones that came to my mind are as follows;

I tried to get answers to questions like these. Of course, the test I wrote is still simple. More advanced than this would be to write tests with mock data in an application with redux connection and API connection and to test the store change. But of course, these are advanced topics. These will do the job for now.

The form where the comment will be written is the component

import { memo, useEffect, useState } from "react";

const INITIAL_STATE = {
  name: "",
  comment: "",
};

const Form = ({ setComments }) => {
  const [values, setValues] = useState(INITIAL_STATE);
  const [buttonDisabled, setButtonDisabled] = useState(true);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });
  };

  useEffect(() => {
    const getValues = Object.values(values);

    const isEmpty = getValues.some((item) => item === "");

    if (isEmpty) setButtonDisabled(true);
    else setButtonDisabled(false);
  }, [values]);

  const onSubmit = () => {
    setComments((prev) => [
      ...prev,
      {
        id: Math.floor(Math.random() * 999999),
        name: values.name,
        comment: values.comment,
      },
    ]);
    // reset values
    setValues(INITIAL_STATE);
  };

  return (
    <div id="form">
      <input
        type="text"
        placeholder="Name"
        name="name"
        value={values.name}
        onChange={handleChange}
      />
      <textarea
        rows={2}
        placeholder="Message"
        name="comment"
        value={values.comment}
        onChange={handleChange}
      ></textarea>
      <button onClick={onSubmit} disabled={buttonDisabled}>
        Submit
      </button>
    </div>
  );
};

export default memo(Form);

Here is a simple component with an input, a textarea and a button. There are some functions required for this component to work. To summarize them;

The form where the comment will be written is the component test

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Form from "./Form";

describe("should render", () => {
  test("input", () => {
    render(<Form />);

    const input = screen.getByPlaceholderText("Name");

    expect(input).toBeInTheDocument();
  });
  test("textarea", () => {
    render(<Form />);

    const textarea = screen.getByPlaceholderText("Message");

    expect(textarea).toBeInTheDocument();
  });
  test("button", () => {
    render(<Form />);

    const button = screen.getByRole("button");

    expect(button).toBeInTheDocument();
  });
});

test("case button dissable props", () => {
  render(<Form />);

  // get elements
  const input = screen.getByPlaceholderText("Name");
  const textarea = screen.getByPlaceholderText("Message");
  const button = screen.getByRole("button");

  expect(button).toBeDisabled();

  userEvent.type(input, "test");
  userEvent.type(textarea, "test");

  expect(button).not.toBeDisabled();
});
Don't get hung up on the explanations of the test cases. Use more explanatory and appropriate expressions :)

Here I proceed step by step as follows;

With the userEvent library, you can enter the input value, create a trigger or upload anything. You can review this page for detailed usage.

I didn't want to write the other component separately. Because they are similar structures, I explained them all from a single component. If you want to examine them, you can examine them in detail on github.

Now it's time for Integration Test

I think unit tests alone are not enough. Because components can be connected to each other. That's why it is necessary to write an end-to-end test with integration test. My preference here is Cypress. Because its writing and the opportunities it offers are 1-0 ahead for me.

After making the necessary installations, it's time to write the test. There are 4 steps in my test. These are;

describe("first test", () => {
  it("post a comment", () => {
    cy.visit("/"); // (1)

    // fill inputs. (2)
    cy.get('input[type="text"]').type("Berat Bozkurt");
    cy.get("textarea").type("This is an example.");

    // click the button (3)
    cy.get("button").click();

    // to check if the comment sented already. (4)
    cy.get("article p").invoke("text").should("eq", "This is an example.");
  });
});
All sources and documents are at the bottom of the text.

After completing the integration test writing process, the test process is carried out either via the terminal or the browser. My preference is to carry out the test process via the browser. Because when I carry out the test process via the browser, if there is an error, I encounter an error message when I encounter that error. This makes it more understandable for me.

Conclusion

Actually, we see that writing tests is not that difficult. We just need to allocate the necessary time for this and determine the cases. Then, we create a higher quality and more robust code structure. Because our cases are clear. If the component will undergo a revision in the future, the probability of creating a bug decreases greatly. Also, with test coverage, we can see how much the components we wrote have been tested. This increases the quality of the project.


Before finishing the article, you can follow me on my social media accounts below. If you have any questions, you can send me a message or a greeting without having to do it..

Resources

Read Next
Open to new job opportunities