개인 프로젝트/HLOG

React에서 테스트 101

홍준혁 [Hong-JunHyeok] 2022. 6. 7. 17:44
728x90

React에서 테스트를 하는 방법을 알아보도록 하겠습니다.

HLOG를 개발하면서 테스트의 중요성을 깨닫게 되었는데요,

가장 중요한 이유는 사람은 언제나 실수를 한다는 점입니다.

사람은 완벽하기 않기 때문에 하나하나 테스트를 하게 되면 굉장히 귀찮은 작업이 될 수 있습니다. 또한, 테스트 과정에서 놓치는 부분이 있을 수도 있죠. 그래서 이러한 테스트 코드를 자동화를 시켜 검증 단계에서 실수할 여지를 줄여주는 것이 중요하다고 생각하게 되었습니다.

두 번째로, 리펙터링을 하는 데 있어서 굉장히 유용하다고 생각하게 되었습니다. 

리펙터링이란, 코드의 기능은 바꾸지 않으면서 내부 구조를 개선하는 작업을 의미하는데요, 이러한 작업이 필요한 이유는 유지보수에 용이하기 때문입니다. 그래서 테스트 코드로 케이스를 작성한 후, 레펙터링을 하고 기존에 명시한 기능과 동일하게 동작하는지 확인할 수 있겠죠. 

이 외에도 테스트 코드는 다양한 이점이 있습니다. 

 

오늘은 프론트엔드에서 테스트를 하는 방법을 알아보도록 하겠습니다. 프론트엔드는 개발환경이 천차만별이므로 환경 설정이 다를 수 있습니다. 참고로 제 개발환경은 React Webpack Typescipt 환경입니다.

테스트의 종류는 크게, 유닛 테스트 통합 테스트 E2E테스트가 있습니다. 그중에서 최소 단위로 기능이 잘 동작하는지 확인하는 유닛 테스트를 해보도록 하겠습니다. 

먼저 Jest를 설치합시다.

yarn add jest
yarn add -D @types/jest

 

const add = (a, b) => a + b;

it('add함수가 잘 동작하는지 테스트합니다.', () => {
  expect(add(3, 6)).toBe(9);
});

src디렉토리 내부에 add.test.ts파일을 만든 후, 다음과 같이 입력합니다.

이제 테스트를 수행하기 위해서 package.json 스크립트를 수정합니다.

"scripts": {
    "build": "webpack",
    "dev": "webpack serve --open --env DEV=true",
    "lint": "eslint './src/**/*.{ts,tsx,js,jsx}'",
    "test": "jest --watchAll --verbose"
  },

 

이제 yarn test로 실행해보면 다음과 같이 동작할 것입니다.

여기서 에러가 나는 분들이 많습니다. 
문제의 원인은 크게 두 가지가 있습니다. ES6문법 에러, TypeScript 관련 에러
해결 방법은 바벨 설정을 해서 트렌스파일링을 진행하면 됩니다.
yarn add -D @babel/preset-env @babel/preset-typescript
그 후에 아래와 같이 작성해줍니다.
// babel.config.js
module.exports = {
	presets: ["@babel/preset-env", "@babel/preset-typescript"]
}

 

이제 실행해보면 정상적으로 동작합니다.

이제 React 컴포넌트를 테스트 해보도록 하겠습니다.

yarn add -D @testing-library/jest-dom @testing-libary/react

위와 같이 입력 후 설치해줍니다.

저희는 react-testing-libary로 컴포넌트 테스트를 진행할 예정입니다.

Footer 컴포넌트를 테스트할 예정입니다.

import React from 'react';
import { render } from '@testing-library/react';

import Footer from '.';

describe('렌더링 테스트', () => {
  it('Footer 컴포넌트가 잘 렌더링 된다.', () => {
    render(<Footer />);
  });
});

이제 스냅샷 테스트를 진행해보도록 하겠습니다.

import React from 'react';
import { render } from '@testing-library/react';

import Footer from '.';

describe('렌더링 테스트', () => {
  it('Footer 컴포넌트가 기존의 스냅샷과 동일하다', () => {
    const utils = render(<Footer />);
    expect(utils.container).toMatchSnapshot();
  });
  it('Footer 컴포넌트에 텍스트가 잘 렌더링 된다.', () => {
    const { getByText } = render(<Footer />);
    expect(getByText('HLOG')).toBeDefined();
  });
});

스냅샷이란 렌더링 된 순간을 기록해 놓은 것인데요, toMatchSnapshot이라는 것은 스냅샷을 서로 비교합니다.

즉, 이후에 컴포넌트를 수정했을 때 원하는 방식으로 렌더링되는지 확인하기 위함입니다.

테스트를 진행하면 다음과 같이 __snapshots__라는 디렉토리에 파일이 생깁니다.

또한 특정 글자가 잘 렌더링 되었는지 테스트또한 가능합니다.

getByText를 사용해서 엘리먼트를 가져와서 그것이 정의되었는지 테스트합니다. 만약 없다면 케이스를 통과하지 못하겠죠.

 

내부적으로 React-Router-Dom을 사용하는 컴포넌트는 어떤 식으로 테스트하는가?

기본적으로 Route는 Router에 감싸져야 합니다. 그래서 그를 묶는 wrapper 함수를 만들어주면 됩니다.

const renderWithRouter = (element: React.ReactNode, route: string) => {
  const history = createMemoryHistory();

  if (route) history.push(route);

  return {
    ...render(
      <Router
        location={history.location}
        navigator={history}
      >
        {element}
      </Router>,
    ),
    history,
  };
};

history도 넘겨줍니다. 그 이유는 history를 이용한 테스팅을 하는 경우가 있기 때문입니다.

예를 들어 pathname을 체크하는 일같이 말이죠.

it('Router가 잘 동작하는지 테스트', async () => {
    const { getByText, history } = renderWithRouter(<HomeTab />, '/recent');
    const popularButton = getByText('인기 게시글');
    fireEvent.click(popularButton);
    await waitFor(() => {
      expect(history.location.pathname).toBe('/');
    });

    const recentButton = getByText('최근 게시글');
    fireEvent.click(recentButton);
    await waitFor(() => {
      expect(history.location.pathname).toBe('/recent');
    });
});

그 다음, Router 클릭시 잘 동작하는지 테스트 하기 위해서 다음과 같이 작성했습니다.

이때, waitFor은 click이벤트가 완료될 때까지 기다려주는 jest헬퍼 함수입니다.

728x90