게시글

강좌10 - React - 3년 전 등록 / 일 년 전 수정

React 테스트(test, jest, enzyme)

조회수:
0
이 포스팅들은 오래되었습니다. 유튜브에서 최신 리액트 강좌 강좌를 보시는 것을 추천합니다.

안녕하세요. 이번 시간에는 React 테스트에 대해 알아보겠습니다.

리액트도 하나의 프레임워크(리덕스와 리액트 라우터와 묶어서)로 사용되는 만큼 테스트가 꼭 필요합니다. 대규모 애플리케이션이 되면 어디에서 에러가 발생할 지 예측하기 힘들어지기 때문이죠. 자바스크립트에는 많은 테스트 라이브러리나 프레임워크가 있지만, 리액트에서는 jestenzyme을 가장 많이 사용합니다.

jest는 테스트 프레임워크고, enzyme은 테스트 라이브러리입니다. jest가 좀 더 큰 개념이죠. enzyme을 jest와 함께 사용할 수도 있습니다. 이번 시간에는 그냥 같이 사용해서 둘 다 간단하게 배워봅시다. babel-jest와 babel-core는 테스트 할 때 ES2015+ 문법을 사용하기 위해 설치합니다. jest는 리액트뿐만 아니라 노드 등을 테스트할 때도 사용할 수 있으므로 알아두시는 게 좋습니다.

npm install --save-dev jest babel-jest babel-core enzyme enzyme-adapter-react-16

바벨7과 같이 사용하고 싶다면 @babel/core과 babel-core@7.0.0-0도 같이 설치해야 합니다.

npm을 통해 설치해줍니다. 개발용으로만 쓰이기 때문에 --save-dev또는 -D입니다.

테스트는 단위 테스트를 할 겁니다. 영어로는 Unit test라고 불리는데요. 한 가지 역할 단위로 테스트하는 방법입니다. 단일 책임 원칙에 따라서 한 가지 역할만 하면 충분합니다. 

먼저 테스트할 간단한 카운터입니다.

counter.jsx

import React, { Component } from 'react';

export default class Counter extends Component {
  state = {
    value: 0,
    title: '',
  }

  changeTitle = (e) => {
    this.setState(() => ({ title: e.target.value }));
  }

  increment = () => {
    this.setState(prevState => ({ value: prevState.value + 1 }));
  };

  render() {
    return (
      <div>
        <input value={this.state.title} id="title" onChange={this.changeTitle} />
        <b>{this.state.value}</b>
        <button id="up" onClick={this.increment}>증가</button>
      </div>
    );
  }
}

테스트 파일을 생성해봅시다.

counter-test.jsx

import React from 'react';
import Counter from './counter.jsx';
import { shallow, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

describe('<Counter />', () => {
  it('성공적으로 렌더링되어야 합니다.', () => {
    const wrapper = shallow(<Counter />);
    expect(wrapper.length).toBe(1);
  });

  it('타이틀 인풋이 렌더링되어야 합니다.', () => {
    const wrapper = shallow(<Counter />);
    expect(wrapper.find('#title').length).toEqual(1);
  });

  it('타이틀이 변경되어야 합니다.', () => {
    const wrapper = shallow(<Counter />);
    wrapper.find('#title').simulate('change', { target: { value: '값' } });
    expect(wrapper.state().title).toBe('값');
  });

  it('숫자가 올라가야 합니다.', () => {
    const wrapper = shallow(<Counter />);
    wrapper.find('#up').simulate('click');
    wrapper.find('#up').simulate('click');
    expect(wrapper.state().value).toBeLessThan(1);
  });
});

enzyme은 react@16과 react@15에서 다르게 동작하기 때문에 enzyme-adapter-react-16과 configure 메서드를 사용해 react@16을 사용하고 있음을 알려주어야 합니다.

describeitjest에서 제공하는 함수입니다. describe는 하나의 테스트 그룹이고, it은 그 안의 작은 단위 테스트입니다. 나중에 결과 화면에서 describe와 it에 넣어준 문자열이 어떻게 보이는지 알게 될 것입니다.

enzyme에는 adapter를 적용하는 configure를 제외하면 크게 세 가지 메소드가 있습니다. shallow, mount, render입니다. API 

  • shallow: 간단한 컴포넌트를 메모리 상에 렌더링합니다. 단일 컴포넌트를 테스트할 때 유용합니다.
  • mount: HOC나 자식 컴포넌트까지 전부 렌더링합니다. 다른 컴포넌트와의 관계를 테스트할 때 유용합니다.
  • render: 컴포넌트를 정적인 html로 렌더링합니다. 컴포넌트가 브라우저에 붙었을 때 html로 어떻게 되는지 판단할 때 사용합니다.

enzyme 메소드로 렌더링한 wrapper는 다양한 기능을 할 수 있습니다. 내부 state나 props에 접근할 수도 있고, 이벤트를 시뮬레이션할 수도 있습니다.

state에 접근하려면 wrapper.state()를 하면 되고, props에 접근하려면 wrapper.props()를 하면 됩니다. 이벤트를 시뮬레이션 하려면 다음과 같이 합니다.

wrapper.find('#up').simulate('click'); // 버튼 클릭
wrapper.find('#up').simulate('click'); // 버튼 클릭
console.log(wrapper.state().value); // 2

wrapper.find('#title').simulate('change', { target: { value: '값' } }); // 인풋에 값 입력
console.log(wrapper.state().title); // '값'

보통 이벤트를 발생시키면 state가 따라서 변합니다. enzyme은 이런 것까지 다 추적해주기 때문에 편리합니다.

shallow 대신 mount를 사용하면 더 다양한 기능을 쓸 수 있지만, 지금같이 간단한 컴포넌트는 shallow로도 충분합니다.

expect는 jest에서 제공하는 것으로 테스트값과 예상값이 일치하는지 여부를 판단합니다. 일치한다면 테스트가 성공한 것이고, 일치하지 않는다면 테스트가 실패한 것입니다. toBe, toEqual, toBeLessThan, toBeGreaterThan 등, 단순 일치뿐만 아니라 이상, 이하, 초과, 미만, truthy, falsy 등을 판단할 수도 있습니다. API 

이제 콘솔에 jest를 입력하면 테스트를 진행합니다. 마지막 it을 틀리게 설정했기 때문에 테스트가 실패합니다.

Error: expect(received).toBeLessThan(expected)
Expected value to be less than:
1
Received:
2

1보다 작을 것을 예상했는데 2가 들어와서 에러가 난 것입니다. toBeLessThan을 toBeGreaterThan으로 수정하면 테스트가 성공합니다.

 PASS counter-test.jsx
<Counter />
✓ 성공적으로 렌더링되어야 합니다.(11ms)
✓ 타이틀 인풋이 렌더링되어야 합니다. (15ms)
✓ 타이틀이 변경되어야 합니다. (22ms)
✓ 숫자가 올라가야 합니다. (25ms)


Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 1.015s

성공했습니다. describe과 it에 적어두었던 문자열이 어떤 테스트인지를 알려줍니다.

컴포넌트가 복잡해질수록 테스트의 중요성도 높아집니다. React jest와 enzyme과 함께라면 걱정 없습니다!

투표로 게시글에 관해 피드백을 해주시면 많은 도움이 됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright © 2016- 무단 전재 및 재배포 금지

댓글

5개의 댓글이 있습니다.
일 년 전
안녕하세요 혹시 홈페이지 오른쪽에 페이스북 타임라인 구현을 어떤식으로 할 수 있는지 알 수 있을가요?? ....
일 년 전
react-facebook입니다!
일 년 전
안녕하세요! 밑에 익명으로 질문했던 사람입니다. 당시에 익명으로 질문을 해서 대댓글이 안되네요 ㅠㅜ. 그. script태그에 js파일을 그냥 넣었더니 처음 화면이나 새로고침을 하는경우에는 js 파일 로딩을 잘 해오는데, react-router로 페이지 이동을 하는 경우에는 js파일을 못 가져와서 적용이 안되네요. 이런거 해결하려면 어떻게 해야할까요? ㅠㅠ
일 년 전
index.html같은 파일에 넣으면 리액트 라우터로 페이지를 이동해도 제대로 적용됩니다. 리액트 라우터는 속만 바꾸는거지 겉 껍데기는 바꾸지 않습니다(head 태그같은 것은 안 바꿔요). 혹시나 페이지 이동 시마다 동적으로 js를 불러오려고 하시는 거면 componentDidMount에서 스크립트 엘리먼트 생성 후 사용하는 꼼수를 쓰셔야 합니다.
일 년 전
확실히 요소 검사로 보니까 리액트 라우터로 이동을 해도 스크립트가 Element에 작성이 되어 있네요... 근데 왜 적용이 안되지...
일 년 전
제가 추가한 js 파일이 jquery랑 jquery를 이용해 작성된 js 파일인데, 이게 문제가 될 수도 있나요?
일 년 전
아닌데... 처음 로딩할땐 잘 되는데.. 두서없이 죄송합니다. ㅠ
일 년 전
안녕하세요! 리액트를 사용해서 간단하게 실습을 해보는 중인데요. 제가 웹디자인 템플릿을 다운받아서 사용을 하다보니 해당 js 파일을 script 태그에 넣어줘야 하는데.. 어떻게 리액트 프로젝트에 넣어줄 수 있을까요??
일 년 전
말 그대로 script 태그에 넣으시면 됩니다. 리액트도 index.html같은 파일은 있습니다. <script src="app.js" />를 하는 그 파일이나 부분요.
일 년 전
안녕하세요! npm 설치 명령이 안먹는 오류는 어떻게 해결해야 할까요?
일 년 전
어떤 명령어가 안 먹는지, 에러 메시지는 무엇인지 알려주세요~
일 년 전
react-lines-ellipsis를 설치하려고 npm이랑 yarn에서 둘 다 시도했는데 warn이 뜨면서 no lisence라고 떠요ㅠㅠ
일 년 전
warn은 에러가 아닙니다. 그냥 경고메시지라서 무시하셔도 됩니다.
2년 전
리액트 테스트....시도해봐야겠네요 아직 한번도 컴포넌트 테스트는 해본적이 ㅎㅎ