React note #19

Yechan Jeon·2022년 1월 2일
0

React dev

목록 보기
18/18
post-thumbnail

Testing react app


Kinds of Testing

  • Manual Testing : Writing code preview and test in browser (still error-prone because it is hard to test all scenarios).

  • Automated Testing : Testing with Code that tests your code. Test individual building blocks of app -> very technical and allows you to check all building blocks.

Kinds of automated tests

  • Unit test ( most important ) : Testing individual building blocks in isolation. Projects typically contain more than dozens of unit tests.

  • Integration tests : Testing the combinations of mutiple building blocks.

  • End to End test (E2E) : Testing complete scenarios in your app as the user might experince it.

Which kind of code should we test? (Unit tests)

What : The smallest building blocks that make up your app.
How : Test success and error cases, also test rare results.

Required tools and setup for testing

Running test and asserting the results : 'Jest'
Simulating (rendering) our react app : 'React Testing Library'

These are already installed with npx install create-react-app

Running a test

  • App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

First argument is test identifier and second is actual testing code.

To run testing, execute this command line(depends on your 'scripts' in pacakge.json)
npm test

If test is successful, you can see this result

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.615 s
Ran all test suites.

You can write all test code in App.test.js, but actually it is better to create separated test code file close to actual building which is tested by code.

For example,
Products.js <-> Products.test.js

Writing test code yourself

Here is three 'A' we need to check.
1. Arrange : set up the test data, test conditions and test enviroment
2. Act : Run logic that should be tested (e.g execution of functions)
3. Assert : Compare execution results with expected functions

  • component
const Greeting = () => {
  return (
    <div>
      <h2>Hello world!</h2>
      <p>Let's Test</p>
    </div>
  );
};

export default Greeting;
  • test code
import { render, screen } from "@testing-library/react";
import Greeting from "./Greeting";

test("renders Hellow World as a text", () => {
  // arrangement
  render(<Greeting />);
  //act
  //assert
  const helloWorldEl = screen.getByText("Hello World!", { exact: false });
  expect(helloWorldEl).toBeInTheDocument();
});

You can group tests related to specific component with global function 'describe()'

define what this test is for and give actual test code as a function argument

describe("Greeting component", () => {
  test("renders Hellow World as a text", () => {
    // arrangement
    render(<Greeting />);
    //act
    //assert
    const helloWorldEl = screen.getByText("Hello World!", { exact: false });
    expect(helloWorldEl).toBeInTheDocument();
  });
});

Testing user interaction & state.

You should identify all the possible scenarios and clarify it in test code.

  • component
const Greeting = () => {
  const [changedText, setChangedText] = useState(false);

  const changeTextHandler = () => {
    setChangedText(true);
  };

  return (
    <div>
      <h2>Hello world!</h2>
      {!changedText && <p>Let's Test</p>}
      {changedText && <p>text changed</p>}
      <button onClick={changeTextHandler}>Change Text!</button>
    </div>
  );
};
  • Greeting.test.js
    You can check event with 'userEvent'
describe("Greeting component", () => {
  test("renders Hello World as a text", () => {
    // arrangement
    render(<Greeting />);
    //act
    //assert
    const helloWorldEl = screen.getByText("Hello World!", { exact: false });
    expect(helloWorldEl).toBeInTheDocument();
  });

  test("renders let's test when state false", () => {
    render(<Greeting />);
    const LetsTestEl = screen.getByText("Let's Test", { exact: false });
    expect(LetsTestEl).toBeInTheDocument();
  });

  test("renders text changed when state true", () => {
    render(<Greeting />);

    //act
    const buttonEl = screen.getByRole("button");
    userEvent.click(buttonEl);

    //asserting
    const ChangedTextEl = screen.getByText("text changed", { exact: false });
    expect(ChangedTextEl).toBeInTheDocument();
  });

  test("Not render let's test if the button was clicked", () => {
    render(<Greeting />);

    //act
    const buttonEl = screen.getByRole("button");
    userEvent.click(buttonEl);

    //asserting
    const ChangedTextEl = screen.queryByText("Let's Test", { exact: false });
    expect(ChangedTextEl).toBeNull();
  });
});

Testing async

For example, when you want to test whether list items is existed, you can use aysnchronous find 'findAllByRole'(There are more methods).

import { render, screen } from "@testing-library/react";
import Async from "./Async";

describe("Async Component", () => {
  test("renders post request", async () => {
    render(<Async />);

    const listItemElements = await screen.findAllByRole("listitem");
    expect(listItemElements).not.toHaveLength(0);
  });
});

By the way, For more information about HTML elements role, Read this article.
HTML element role

But , Regarding to http requests, it is not ideal send requests whenever we test the component(It will cause heavy network traffic).
Instead of that you send real requests, you can set mock function in test code.

  1. Assign jest.fn() to window.fetch (if you take care of fetch)
  2. use mockResolvedValueOnce (Because we have resolved response and data from response.json() in real http requests)
  3. set mock data inside of above method

Then this mock function imitate our real fetch process , so we can check whether there are list items or not without sending real request.

describe("Async Component", () => {
  test("renders post request", async () => {
    window.fetch = jest.fn();
    window.fetch.mockResolvedValueOnce({
      json: async () => [{ id: "p1", title: "First post" }],
    });
    render(<Async />);

    const listItemElements = await screen.findAllByRole("listitem");
    expect(listItemElements).not.toHaveLength(0);
  });
});

More resources about testing app

Jest docs
React testing library
React hooks testing

profile
방황했습니다. 다시 웹 개발 하려고요!

0개의 댓글