[React] Test 환경 구축과 테스트 예제
공부내용 공유하기

[React] Test 환경 구축과 테스트 예제

React Testing

💡 Jest, ReactTestingLibrary로 Testing 환경을 구축했던 경험을 공유한다.

도입 취지

TDD(Test Driven Development)가 아니라도 테스트를 통해 비즈니스의 핵심 로직 검증만이라도 가능하다면, 코드의 리팩토링이나 기능의 추가, 수정 시에 더욱 안정성을 가질 수 있다고 생각되었음.

공통적인 테스트 코드 환경 구축 및 컨벤션이 있다면 최소한의 노력으로 최대한의 효과를 볼 수 있을거라 기대됨.

환경 구축

설치

💡 CRA기준으로는 이미 jest가 설치되어 있고, 다른 툴체인에서는 jest를 설치해준다.

그 외 설치 필요한 항목

  • yarn add@testing-library/react@types/jest
  • ts-jest
  • @testing-library/user-event
  • @testing-library/jest-dom

test 환경을 커스텀하기 위해 eject를 권장한다.

config 세팅

  1. eject 후 package.json에 jest 항목을 찾을 수 있다.
  2. jest 항목을 삭제한다.
  3. root 경로에 jest.config.js를 생성해준다.
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig");

module.exports = {
  preset: "ts-jest",
  globals: {
    __DEV__: true,
  },
  collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
  setupFiles: ["react-app-polyfill/jsdom"],
  testMatch: [
    "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
    "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}",
  ],
  testEnvironment: "jsdom",
  testRunner: "<rootDir>/node_modules/jest-circus/runner.js",
  transform: {
    "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",
    "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
    "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
      "<rootDir>/config/jest/fileTransform.js",
  },
  transformIgnorePatterns: ["^.+\\.module\\.(css|sass|scss)$"],
  moduleFileExtensions: [
    "web.js",
    "js",
    "web.ts",
    "ts",
    "web.tsx",
    "tsx",
    "json",
    "web.jsx",
    "jsx",
    "node",
  ],
  watchPlugins: [
    "jest-watch-typeahead/filename",
    "jest-watch-typeahead/testname",
  ],
  resetMocks: true,
  roots: ["<rootDir>"],
  modulePaths: ["<rootDir>"],
  moduleDirectories: [".", "src", "node_modules"],
  testTimeout: 10000,
  moduleNameMapper: {
    "^react-native$": "react-native-web",
    "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
    ...pathsToModuleNameMapper(compilerOptions.paths),
  },
};

pathsToModuleNameMapper 메소드는 tsconfig.json에 있는 절대 경로 세팅을 jest에서도 인식이 가능하게 처리하는 메소드이다. tsconfig.json에서 절대경로 세팅을 하지 않는 경우에는 제외해도 된다.

테스트

src/pages/login.tsx

import React, { useState } from "react";

export interface Props {}

const Login: React.FC<Props> = (props: Props) => {
  const [id, setId] = useState("");
  const [isOk, setIsOk] = useState(false);

  const handleSubmit = async () => {
    setTimeout(() => {
      setIsOk(true);
    }, 500);
  };

  return (
    <div>
      {isOk && <h1>통과되었습니다.</h1>}
      <input
        data-testid={"loginId"}
        name={"id"}
        value={id}
        onChange={(e) => setId(e.target.value)}
      />
      <button onClick={handleSubmit}>제출하기</button>
    </div>
  );
};

export default Login;

 

 

About Queries | Testing Library

Overview

testing-library.com

 

src/test/login/index.test.tsx

import { fireEvent, render, screen } from "@testing-library/react";
import Login from "../../pages/login";
import React from "react";

describe("로그인 화면 테스트", () => {
  it("아이디 입력 후 제출 테스트", async () => {
    // 로그인 컴포넌트 렌더링
    render(<Login />);
    // 렌더링 된 컴포넌트에서 select 메소드 사용
    // test id로 element 가져오기
    const idInput = screen.getByTestId("loginId") as HTMLInputElement;
    // change event로 input 값 변경
    fireEvent.change(idInput, { target: { value: "testid1234" } });
    expect(idInput.value).toEqual("testid1234");

    // 제출버튼 찾아서 제출
    const submitButton = screen.getByText("제출하기");
    fireEvent.click(submitButton);

    screen.logTestingPlaygroundURL();

    // 클릭 이벤트에 따른 비동기 작업 발생 -> 기다렸다가 통과 확인
    await screen.findByText("통과되었습니다.");
  });
});

이외 고려 사항들 (Other Considerations)

  • react-native 환경에서 테스트를 하기 위해서는 추가적인 환경 세팅이 필요하다.
  • react-dom 환경에서의 테스트는 screen.getByText 등으로 현재 렌더링 된 화면에 직접 접근이 가능해서 편리하다.
  • Redux, Styled-Components 등의 Provider가 필요한 경우에는 추가적으로 render 쪽에서 Provider를 넘겨주던지, HOC를 만들어서 작업해야 한다.