• [React] TDD개발로 로그인 폼 테스트하기(2)

    2023. 11. 11.

    by. 지은이: 김지은

    728x90

     

    이전 글 보기

    이전에 필드 테스트까지 했다면 이번엔 필드에 입력 후 로그인 버튼을 누르면 가짜 로그인 프로세스를 만들려고 한다.

     

    https://jsonplaceholder.typicode.com/

    JSON placeholder는 가짜 REST API를 제공하며, DB와 연동하지 않아도 HTTP메서드(GET, POST, PUT, DELETE 등)를 지원한다.

    그래서 JSON placeholder에서 user 데이터 중 아이디가 1인 user 정보를 받아오려고 한다.

    https://jsonplaceholder.typicode.com/users/1https://jsonplaceholder.typicode.com/users/1

     

    1. API 테스트

    npm install axios 설치하기

    * CRA에서 axios 설치 후 test 하면 오류가 발생하는데 이는 axios 버전이 올라가면서 ECMAScript 문법을 사용하는데 Jest는 Node.js 환경에서 동작하기에 CommonJS 문법을 사용해서 import에서 오류가 발생한다.

     

    그래서 package.json파일에서 transformIgnorePatterns를 설정하는데 아래와 같은 정규식으로 axios를 제외한 다른 모듈을 변환하지 않도록 설정

    "jest": {
        "transformIgnorePatterns": [
          "node_modules/(?!axios)"
        ]
      },

     

    출처: https://junhyunny.github.io/react/jest/module-import-error-on-jest/?ref=codenary

     

    import axios from "axios";
    import React, { useState } from "react";
    
    const Login = () => {
      const [error, setError] = useState(false);
      const [loading, setLoading] = useState(false);
      const [user, setUser] = useState({});
    
      const handleClick = async (e) => {
        e.preventDefault();
        setLoading(true);
        try {
          const { data } = await axios.get(
            "https://jsonplaceholder.typicode.com/users/1"
          );
          setUser(data);
        } catch {
          setError(true);
        }
        setLoading(false);
      };
    
      return (
        <div className="login">
          <p>{user.name}</p>
          <form className="container">
            ...
            <button
              disabled={!email || !password}
              type="submit"
              onClick={handleClick}
            >
              {loading ? "잠시만 기다려주세요" : "로그인"}
            </button>
          </form>
        </div>
      );
    };
    
    export default Login;

    axios로 user 데이터를 가져와서 상태관리하고, user name을 화면에 보이게 했다.

    또한, 데이터를 가져오는 동안 로딩상태를 표시해서  버튼에 '잠시만 기다려주세요'를 보이게 한다.

     

    // '잠시만 기다려주세요' 문구의 로딩 상태가 표시되지 않는지 테스트
    test("loading should not be rendered", () => {
      render(<Login />);
      const buttonEl = screen.getByRole("button");
      expect(buttonEl).not.toHaveTextContent("잠시만 기다려주세요");
    });
    
    // 버튼 클릭 시 로딩 상태가 표시되는지 테스트
    test("loading should be rendered when click", () => {
      render(<Login />);
      const buttonEl = screen.getByRole("button");
    
      const emailInputEl = screen.getByPlaceholderText("이메일");
      const passwordInputEl = screen.getByPlaceholderText("비밀번호");
    
      const testValue = "test";
      fireEvent.change(emailInputEl, { target: { value: testValue } });
      fireEvent.change(passwordInputEl, { target: { value: testValue } });
      fireEvent.click(buttonEl);
      expect(buttonEl).toHaveTextContent("잠시만 기다려주세요");
    });

    위 테스트는 PASS지만, 사실 handleClick 함수가 비동기적으로 동작하기 때문에 테스트 코드는 비동기 작업을 기다리지 않고 즉시 테스트된다.

    또한 외부 의존성을 실제 네트워크 호출 대신 mock으로 대체해서 테스트의 격리성을 확보하도록 한다.

     

    2. jest.mock

    src/__mocks__/axios.js 파일 생성

    https://mulder21c.github.io/jest/docs/en/next/manual-mocks

    import { fireEvent, render, screen, waitFor } from "@testing-library/react";
    
    // axios 모킹 설정
    jest.mock("axios", () => ({
      __esModule: true,
      default: {
        get: () => ({
          data: { id: 1, name: "John" },
        }),
      },
    }));
    
    
    test("loading should not be rendered after fetching", async () => {
      render(<Login />);
      const buttonEl = screen.getByRole("button");
    
      const emailInputEl = screen.getByPlaceholderText("이메일");
      const passwordInputEl = screen.getByPlaceholderText("비밀번호");
    
      const testValue = "test";
      fireEvent.change(emailInputEl, { target: { value: testValue } });
      fireEvent.change(passwordInputEl, { target: { value: testValue } });
      fireEvent.click(buttonEl);
      await waitFor(() =>
        expect(buttonEl).not.toHaveTextContent("잠시만 기다려주세요")
      );
    });

    jest.mock 함수를 사용해서 axios를 모킹.

    모킹을 사용하면 테스트 중에 의도적으로 특정한 상황을 시뮬레이션하거나, 특정한 값을 반환하도록 강제할 수 있다.

    그래서 get 메서드가 호출되면 실제로 네트워크 요청을 하지 않고 가짜 응답인 { id: 1, name: "John" }가 반환된다.

     

    waitFor은 주로 비동기 작업을 테스트할 때 사용되며 특정 조건이 충족될 때까지 대기하고 충족되면 다음 단계를 진행한다.

    만약 일정 시간이 경과해도 만족되지 않는다면 테스트는 실패

    그래서 '잠시만 기다려주세요' 로딩 메세지가 존재하지 않을 때까지 기다린 후 테스트는 통과가 된다.

     

    3. User 테스트

    위에서 가짜 데이터로 user의 name을 John이라고 설정했기 때문에 John이라는 사용자를 비동기적으로 가져온 후 화면에 제대로 렌더링 되었는지 확인하려고 한다.

     

    test("user should be rendered after fetching", async () => {
      render(<Login />);
      const buttonEl = screen.getByRole("button");
    
      const emailInputEl = screen.getByPlaceholderText("이메일");
      const passwordInputEl = screen.getByPlaceholderText("비밀번호");
    
      const testValue = "test";
      fireEvent.change(emailInputEl, { target: { value: testValue } });
      fireEvent.change(passwordInputEl, { target: { value: testValue } });
      fireEvent.click(buttonEl);
    
      const userItem = await screen.findByText("John");
      expect(userItem).toBeInTheDocument();
    });

    screen.findByText()는 텍스트를 찾을 때까지 대기 후 해당 텍스트가 화면에 표시될 때까지 테스트를 기다린다.

    그래서 setUser(data) 부분을 제거하면 화면에 렌더링 되지 않기 때문에 테스트는 실패하게 된다.

     

     

    댓글