[React] 벨로퍼트와 함께하는 React Testing - 비동기 작업 테스트

2023. 1. 8. 15:08·React/실험실
반응형

들어가며

벨로퍼트님의 테스팅 튜토리얼을 공부한 내용을 정리하는 글입니다. 

 

비동기적으로 바뀌는 컴포넌트 UI 테스트

const { useState, useEffect } = require("react");

const DelayedToggle = () => {
  const [toggle, setToggle] = useState(false);

  useEffect(() => {
    setInterval(() => {
      setToggle((prev) => !prev);
    }, 1000);
  }, []);

  return (
    <div>
      상태 : <span>{toggle ? "On" : "Off"}</span>
      {toggle && <div>토글이 켜졌다!</div>}
    </div>
  );
};

export default DelayedToggle;

다음과 같이 1초마다 상태 값이 바뀌는 컴포넌트가 있습니다. 

비동기가 포함된 컴포넌트는 어떤 방식으로 테스트를 해야할까요? 

 

Async Utilities 

react-testing-library는 Async Utilities 함수들을 제공해서 비동기 테스트를 할 수 있습니다. 

Async Utilities는 2가지 함수가 있다. 

 

waitFor

waitFor 함수를 사용하면 특정 콜백에서 에러를 발생하지 않을 때 까지 대기할 수 있습니다. 

  test("reveals text when toggle is On", async () => {
    render(<DelayedToggle />);

    const checkToggleText = () => {
      return screen.getByText("토글이 켜졌다!");
    };

    await waitFor(checkToggleText, { timeout: 2000 });
  });

waitFor 함수는 콜백 안의 함수가 에러가 발생하지 않을 때 까지 기다리다가, 대기 시간이 초과되면 

테스트 케이스가 실패한다. 

timeout의 기본값은 4500ms이며, 이는 변경할 수 있다. 

 

waitForElementToBeRemoved

waitForElementToBeRemoved 함수는 특정 엘리먼트가 화면에서 사라질때까지 기다리는

함수이다. 

  test("removes text when toggle is OFF", async () => {
    render(<DelayedToggle />);

    const toggleButton = screen.getByText("토글");
    fireEvent.click(toggleButton);

    screen.getByText("토글이 켜졌다!");

    await waitForElementToBeRemoved(
      () => screen.queryByText("토글이 켜졌다!"),
      { timeout: 2000 }
    );
  });

테스트 방식은 waitFor과 유사하다. 

 

REST  API 호출 시 테스트 

다음으로 REST API를 사용하는 경우 어떤 방식으로 테스트를 해야할까?

테스트를 위해서 먼저 라이브러리를 설치한다. 

yarn add axios

 

JSONPlaceholder에서 제공하는 가짜 API를 사용할 계획이다. 

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

const RestAPI = ({ id }) => {
  const [userData, setUserData] = useState(null);
  const [loading, setLoading] = useState(false);

  const getUser = async (id) => {
    setLoading(true);

    try {
      const response = await axios.get(
        `https://jsonplaceholder.typicode.com/users/${id}`
      );

      setUserData(response.data);
    } catch (error) {
      console.log(error);
    }

    setLoading(false);
  };

  useEffect(() => {
    getUser(id);
  }, [id]);

  if (loading || !userData) return <div>Loading...</div>;

  const { username, email } = userData;

  return (
    <div>
      <p>UserName : {username}</p>
      <p>email : {email}</p>
    </div>
  );
};

export default RestAPI;

id 값을 props로 받아와서 API를 호출하고 그 결과 username과 email을 보여주는 컴포넌트이다. 

 

REST API를 호출해야 하는 컴포넌트의 경우, 테스트 코드에서도 똑같이 요청을 보낼 수 있지만, 

일반적으로 서버에서 API를 직접 호출하지 않고 mocking한다. 

 

그 이유는 아직 서버의 API가 만들어지지 않은 경우, 무작정 기다리기보단 mocking 서버에서

API 명세에 따라 데이터를 넘겨주는 방식으로 빠르게 개발이 가능하기 때문이다. 

 

mocking으로 테스트하는 방식은 다양하지만 msw 를 사용해서 진행하려고 한다. 

그 이유는 axios-mock-adapter 등 보다 msw를 사용하면 실제 사용자처럼 테스트가 가능하기

때문이다. 

 

실제 사용자처럼 테스트할 수 있다는 것은 axios-mock-adapter 같은 경우 

  mock.onGet('https://jsonplaceholder.typicode.com/users/1').reply(200, {
    id: 1,
    name: 'Leanne Graham',
    username: 'Bret',
    email: 'Sincere@april.biz',
    address: {
      street: 'Kulas Light',
      suite: 'Apt. 556',
      city: 'Gwenborough',
      zipcode: '92998-3874',
      geo: {
        lat: '-37.3159',
        lng: '81.1496'
      }
    },
    phone: '1-770-736-8031 x56442',
    website: 'hildegard.org',
    company: {
      name: 'Romaguera-Crona',
      catchPhrase: 'Multi-layered client-server neural-net',
      bs: 'harness real-time e-markets'
    }
  });

다음과 같은 방식으로 mocking 한다. 

 

하지만 msw 를 사용하면 query, params, body 등에 따라 넘겨주는 방식을 선정할 수 있기 때문에 

실제로 사용한다고 했을 때, 서버 API가 작업이 완료되어 연동하는 경우에 코드를 변경할 필요가

없다는 장점이 있기 때문이다. 

 

msw의 자세한 내용은 앞서 작성해둔 글이 있으므로 빠르게 넘기겠다. 

import { rest } from "msw";

const data = [
  {
    id: "1",
    name: "Leanne Grahqweqweqweam",
    username: "Bret",
    email: "Sincere@april.biz",
    address: {
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874",
      geo: {
        lat: "-37.3159",
        lng: "81.1496",
      },
    },
    phone: "1-770-736-8031 x56442",
    website: "hildegard.org",
    company: {
      name: "Romaguera-Crona",
      catchPhrase: "Multi-layered client-server neural-net",
      bs: "harness real-time e-markets",
    },
  },
  // ...
];

const handlers = [
  rest.get(
    `https://jsonplaceholder.typicode.com/users/:id`,
    (req, res, ctx) => {
      const { id: userId } = req.params;

      const result = data.filter(({ id }) => userId === id);

      return res(ctx.status(201), ctx.json(result[0]));
    }
  ),
];

export default handlers;

다음과 같이 handler를 사용할 계획이다. 

 

import { render, screen, waitFor } from "@testing-library/react";
import { setupServer } from "msw/node";
import { rest } from "msw";
import RestAPI from ".";

const data = [
  {
    id: "1",
    name: "Leanne Graham",
    username: "Bret",
    email: "Sincere@april.biz",
    address: {
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874",
      geo: {
        lat: "-37.3159",
        lng: "81.1496",
      },
    },
    phone: "1-770-736-8031 x56442",
    website: "hildegard.org",
    company: {
      name: "Romaguera-Crona",
      catchPhrase: "Multi-layered client-server neural-net",
      bs: "harness real-time e-markets",
    },
  },
  {
    id: "2",
    name: "Leanne Graham",
    username: "Bret",
    email: "Sincere@april.biz",
    address: {
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874",
      geo: {
        lat: "-37.3159",
        lng: "81.1496",
      },
    },
    phone: "1-770-736-8031 x56442",
    website: "hildegard.org",
    company: {
      name: "Romaguera-Crona",
      catchPhrase: "Multi-layered client-server neural-net",
      bs: "harness real-time e-markets",
    },
  },
];

const server = setupServer(
  rest.get(
    "https://jsonplaceholder.typicode.com/users/:id",
    (req, res, ctx) => {
      const { id: userId } = req.params;

      const result = data.filter(({ id }) => userId === id);

      return res(ctx.status(201), ctx.json(result[0]));
    }
  )
);

describe("REST API", () => {
  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  test("calls getUser API loads userData", async () => {
    render(<RestAPI id={1} />);
    await waitFor(function () {
      return screen.getByText("Loading...");
    });
    await waitFor(function () {
      return screen.getByText("Bret");
    });
  });
});

msw 를 react-testing-library와 사용하기 위해서는 setupServer를 사용해야한다.

 

그리고 beforeAll을 통해서 모든 테스트 전에 서버를 실행 시켜주었다. 

afterEach를 통해 종료되기 전 mocking 서버를 초기화 시키고 afterAll를 사용해 모든 테스트 후

mocking 서버를 종료하였다. 

반응형
저작자표시 비영리 변경금지 (새창열림)

'React > 실험실' 카테고리의 다른 글

[React] styled-reset  (0) 2023.01.15
[React] 나만의 알고리즘 문제 저장소 만들기 - 시작  (0) 2023.01.13
[React] 벨로퍼트와 함께하는 React Testing - Todo List  (0) 2023.01.07
[React] 벨로퍼트와 함께하는 React Testing - react-testing-library  (0) 2023.01.06
[React] 벨로퍼트와 함께하는 React Testing - TDD  (1) 2023.01.05
'React/실험실' 카테고리의 다른 글
  • [React] styled-reset
  • [React] 나만의 알고리즘 문제 저장소 만들기 - 시작
  • [React] 벨로퍼트와 함께하는 React Testing - Todo List
  • [React] 벨로퍼트와 함께하는 React Testing - react-testing-library
잉여개발자
잉여개발자
풀스택 개발자를 목표로 잉여롭게 개발 공부도 하면서 다양한 취미 생활도 즐기고 있는 잉여 개발자입니다.
  • 잉여개발자
    잉여로운 개발일지
    잉여개발자
    • 분류 전체보기 (789)
      • 개발정보 (36)
      • 개발환경 (7)
      • 개발생활 (19)
      • React (141)
        • 이론 (23)
        • 기능 (12)
        • 실험실 (88)
        • 버그 (6)
        • 패스트캠퍼스 (9)
        • Npm (3)
      • React Native (28)
        • 공통 (6)
        • TypeScript (3)
        • JavaScript (18)
        • 버그 (1)
      • Next.js (30)
        • 이론 (13)
        • 실험실 (13)
        • 버그 (3)
      • Web (35)
      • 알고리즘 (202)
        • 풀이 힌트 (39)
      • JavaScript (47)
      • TypeScript (29)
        • 기초 (27)
        • 실험실 (2)
      • Node.js (13)
        • 이론 (0)
        • 기능 (3)
        • 실험실 (9)
        • 버그 (1)
      • 도커 (4)
      • CCNA (22)
        • 이론 (4)
        • 문제 (18)
      • 취미생활 (167)
        • 잉여로운 칵테일 (2)
        • 잉여의 식물키우기 (130)
        • 잉여로운 여행기 (11)
        • 잉여의 제2외국어 (21)
        • 잉여로운 책장 (2)
      • Java (1)
        • Java의 정석 (1)
      • 꿀팁 공유 (3)
  • 태그

    바질 키우기
    CCNA
    Babel
    next.js
    타일러영어
    리얼학습일기
    Node.js
    다이소
    CSS
    네트워크
    ReactNative
    영어독학
    react
    Docker
    바질
    자바스크립트
    식물
    redux
    typescript
    알고리즘
    네이버 부스트캠프
    타입스크립트
    webpack
    리액트
    영어회화
    리얼클래스
    ChatGPT
    javascript
    프로그래머스
    덤프
  • hELLO· Designed By정상우.v4.10.1
잉여개발자
[React] 벨로퍼트와 함께하는 React Testing - 비동기 작업 테스트
상단으로

티스토리툴바