Testing React, Redux App




Contents

  • Introduction
  • Installing Dependencies
  • File Structure
  • What to Test?
  • Unit Test
  • Test with JSDOM in Create React App
  • Limiting Test Knowledge
  • Test widh Enzyme

Introduction

대부분의 엔지니어링에서는 정교함의 척도로 수학이 사용된다고 합니다. 컴퓨터 공학도 수학에 깊은 뿌리가 있지만, 직접적으로 사용하는 경우는 드물다고 합니다. 그러면, 어떠한 방법으로 자신이 작성한 시스템이 적절하게 작동하는지 파악할 수 있을까요? 프로그래머 분야에서는 테스트 작성이 바로 정교함의 척도로 작동한다고 합니다.

Installing Dependencies

npm install --save redux react-redux

File Structure

public
|__index.html
|__favicon.ico
src
|__components
|      |__ __text__
|      |       |__App.test.js
|      |__ App.js
|      |__ CommentBox.js
|      |__ CommentList.js
|__ index.tsx
|__ package.json

What to Test?

앱의 각 부분별 테스트 작성을 위해, 먼저 앱의 작은 부분 단위가 앱에서 어떠한 역할을 하는지 생각해 볼 수 있습니다. 그리고 각 컴포넌트 또는 reducer 함수 등 각 부분이 기대하는 역할에 맞게 실행되는지 테스트를 진행할 수 있습니다. 간단한 예를 들면, input tag가 있고 버튼을 클릭했을 때 input tag의 값이 초기화 되어야 한다면, 이 코드가 제대로 생각대로 진행되는지 테스트를 해 볼 수 있습니다.

즉, 우리는 앱의 각 unit이 어떠한 역할을 하는지 음미해보고 기대하는 역할을 잘 수행하는지 점검하는 코드를 작성할 수 있습니다.

Unit Test

Unit Test란 여러분이 작성한 시스템의 부품들(기본적으로 함수)을 테스트 하는것을 말합니다. 사소한 함수를 제외한 모든 함수에 유닛 테스트를 해야 한다고 합니다. 테스트를 하느라 시간이 더 소요되지만, 오히려 나중에 테스트를 안 해서 버리게 되는 시간이 더 많아질 수도 있습니다. 또한, 테스트를 하며 시스템 각각의 부품들의 역할이 무엇인지 더 명확하게 이해할 수 있습니다.

Test with JSDOM in Create React App

일단 첫 테스트 케이스를 작성하며, 테스트하는 방법을 익혀보도록 하겠습니다. 테스트를 하려면 대상 컴포넌트가 있어야 할 것입니다. 그래서, 먼저 src/components 폴더에 기본적인 컴포넌트를 만들도록 하겠습니다.

// src/components/CommentBox.js
import React from 'react';

export default () => {
  return <div>Comment Box</div>;
};

// src/components/CommentList.js
import React from 'react';

export default () => {
  return <div>Comment List</div>;
};

위와 같이 CommentBox, CommentList 두 컴포넌트를 만들었습니다. CommentBox는 코멘트를 입력할 수 있는 input 컴포넌트이며, 이 값을 받아 CommentList는 리스트를 보여줍니다. 그리고 두 컴포넌트를 렌더하는 App 컴포넌트를 만들고 이를 index.js파일에서 React가 렌더할 수 있도록 파일을 생성합니다.

// src/components/App.js
import React from 'react';
import CommentBox from './CommentBox';
import CommentList from './CommentList';

const App = () => {
  return (
    <div>
      <CommentBox />
      <CommentList />
    </div>
  );
};

export default App;

// src/components/App.js
import React from 'react';
import ReactDOM from 'react-dom';

import App from './components/App';

ReactDOM.render(<App />, document.querySelector('#root'));

기본적인 컴포넌트를 생성했으니, 이제 첫 테스트 코드를 작성할 수 있습니다. "test" 폴더에 "App.test.js" 파일을 생성하고 테스트 케이스 한 개를 아래와 같이 생성합니다.

import React from 'react';
import ReactDOM from 'react-dom';
import App from '../App';

it('shows a comment box', () => {
  const div = document.createElement('div');

  ReactDOM.render(<App />, div);

  expect(div.innerHTML).toContain('Comment Box');

  ReactDOM.unmountComponentAtNode(div);
});

테스트 케이스를 설명하기 전, 한 가지 짚고 넘어가야 할 것이 있습니다. 웹앱에서 React는 기본적으로 브라우저에서 돌아가는 것을 기대합니다. 하지만, 우리가 진행하는 테스트는 브라우저가 아닌 터미널 상에서 진행합니다. 위의 테스트 코드는 브라우저가아닌 터미널 상에서 실행을 하는데, document를 이용해 새로운 "div" 요소를 생성했습니다.

이는 Create React App에는 기본적으로 JSDOM이 내장되어 있기 때문입니다. JSDOM은 Node.js 상에서 브라우저와 비슷한 환경을 만들어 여러 작업을 할 수 있게 해주는 라이브러리입니다. 지금의 경우 터미널 상에서 div를 생성해 컴포넌트를 렌더하고 테스트를 진행하게 도와주고 있습니다. 이 div는 브라우저가 아닌 온전히 메모리 상에만 존재하는 것입니다.

테스트를 여려 차례 진행하면 테스트 성능에 대해서도 고려하게 됩니다. 따라서, 테스트 완료 후 일시적으로 생성했던 객체 또는 컴포넌트를 정리해 주어야 합니다. 우리는 unmountComponentAtNode를 통해 컴포넌트를 unmount 시켰습니다.

다시 테스트 코드로 돌아가서 "div.innerHTML"을 콘솔에 출력하면 아래와 같은 결과를 확인할 수 있습니다. 우리는 렌더한 App 컴포넌트 내부에 CommentBox 컴포넌트가 포함돼 있는지 확인하기 위해 위와 같은 테스트 코드를 작성했습니다.

console.log src/components/__tests__/App.test.js:9
  <div><div>Comment Box</div><div>Comment List</div></div>

Limiting Test Knowledge

짐작하셨을수도 있지만, 위의 테스트 케이스는 좋은 테스트 케이스는 아닙니다. 위 테스트는 App 컴포넌트를 렌더하고 App 컴포넌트의 Children Component인 CommentBox 컴포넌트의 내부 콘텐츠 내용이 맞는지 확인을 하고 있습니다. 이 경우 자식 컴포넌트의 내용이 수정되면, 테스트 케이스 또한 수정을 해야합니다. 이러한 방식은 이상적이지 않습니다. 위와 같이 App 컴포넌트가 자식 컴포넌트 내부의 세부 내용까지 알 필요는 없습니다. 단순히 자식 컴포넌트가 존재하는 것을 인지하고 있는 것만으로도 충분합니다. 그럼 다시 코드를 좀 더 이상적으로 수정해 보도록 하겠습니다.

Test with Enzyme

Enzyme은 Airbnb에서 만든 오픈 소스 패키지로 React 컴포넌트를 테스트하는데 도움을 줍니다. 그럼 enzyme을 설치합니다. enzyme-adapter-react-16의 뒤 숫자는 현재 제가 사용하고 있는 react의 버전입니다. 만약 여러분이 사용하고 있는 react 버전이 다르다면, 해당하는 버전에 맞게 숫자를 변경해서 설치하셔야 합니다.

npm install --save enzyme enzyme-adapter-react-16

enzyme 설치 후 enzyme 초기 설정 파일을 생성하겠습니다. enzyme 초기 세팅을 이 파일에서 함으로써 각 컴포넌트 별 테스트 파일에서 중복된 작업을 생략할 수 있습니다. src 폴더 안에 "setupTests.js" 파일을 생성하겠습니다.

// src/setupTests.js
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

위 설정 파일 생성 시 파일명에 주의해야합니다. 테스트 실행 시 src폴더 안의 setupTest.js 파일이 먼저 실행된 후 enzyme 테스트 파일이 실행됩니다.

우리는 shallow, full DOM, static 세 가지 방법으로 enzyme을 활용해 테스트 시 컴포넌트를 렌더할 수 있습니다.


  • static render 함수는 component를 받아 렌더 후 html을 생성하고 이것을 반환합니다. 반환 받은 html과는 어떠한 상호작용을 할 수 없습니다.
  • shallow render는 컴포넌트 인스턴스를 반환합니다. shallow render는 전달 받은 컴포넌트의 자식 컴포넌트까지 렌더하지는 않습니다. 컴포넌트 한 개만 독립적으로 테스트를 할 때 사용할 수 있습니다.
  • full DOM render는 자식 컴포넌트까지 렌더를 합니다. 앱 전체를 복사 후 앱 전체를 테스트 할 때 사용하면 좋습니다.

그럼 Enzyme을 사용해 App.test.js 파일을 수정해 보도록 하겠습니다.

import React from 'react';
import { shallow } from 'enzyme';
import App from '../App';
import

it('shows a comment box', () => {
  const wrapped = shallow(<App />);

  expect(wrapped.find(CommentBox).length).toEqual(1);
});

jest와 enzyme을 이용해 App 컴포넌트를 렌더한 후, App 컴포넌트 안에 CommentBox가 위치해 있는지 확인하는 테스트입니다.