상세 컨텐츠

본문 제목

react testing library와 jest로 React 유닛 테스트 구현하기

개발

by 호박너구리의 블로그 2021. 5. 21. 11:10

본문

 

최근들어 TDD (test-driven development, 테스트 주도 개발) 라는 말이 더 자주 들리는 것 같습니다.

 

실패하는 테스트 코드를 만들어놓고,

그 테스트를 통과하는 진짜 코드를 작성하고,

수정할 것이 있으면 리팩토링하는 방식인데요.

그렇게 하면 작은 기능 단위로 개발하게 되기 떄문에 깔끔하고 좋은 코드를 유지할 수 있게 됩니다.

 

 

1. 테스트의 종류: 유닛 테스트란?

 

프론트엔드 테스트는 크게 세 가지 종류가 있습니다.

 

E2E(End to End) 테스트는 프로젝트가 브라우저 위에서 제대로 작동하는지 사용자 관점에서 테스트하는 방법입니다. 보통 셀레니움(Selenium)이나 퍼페티어(Puppeteer) 등을 사용합니다.

 

통합(Integration) 테스트는 기능 단위로 묶어서 테스트하는 방법으로, 보통 유닛 테스트가 끝난 모듈을 묶어서 확인합니다. 여러 컴포넌트들이 잘 상호작용하고 렌더되는지, DOM 이벤트가 발생했을 때 원하는 UI 변화가 일어나는지 등을 체크하는 것이죠.

 

유닛 테스트는 최소 단위로 기능이 잘 작동하는지 확인하는 방법인데요. 컴포넌트가 잘 렌더되는지, 특정 함수가 잘 작동하는지 등을 보는 것입니다. 그리고 오늘은 유닛 테스트에 대해서 살펴보려고 합니다.

 

 

2. 리액트 테스팅 툴

보통 자바스크립트를 테스트하기 위해서는 다음과 같은 도구들을 사용할 수 있습니다.

  • Karma
  • Jasmine
  • Jest
  • Chai
  • Mocha

저는 Jest를 많이 사용하는데요, Jest는 설치 및 시작이 간편합니다. 그리고 페이스북에서 제작한 프레임워크라 React랑 잘 맞는 것도 같아요. 

 

그리고 DOM을 테스트하기 위한 도구로는 에어비앤비에서 2015년부터 개발한 Enzyme 과 2018년부터 개발된 react-testing-library 가 가장 유명합니다. 

 

Enzyme 과 react-testing-library 는 둘 다 컴포넌트 테스트를 해주지만 각기 다른 철학을 갖고 있습니다. Enzyme 을 사용할 때에는 컴포넌트의 내부의 props, state를 확인하는 등 컴포넌트의 내부 기능에 자주 접근합니다. 반면에 react-testing-library는 렌더된 결과에 보다 집중합니다. 컴포넌트 내부의 동작보다는 실제 화면에 어떤 것이 보여지는지, DOM에 대해 신경을 쓰는 것이죠.

 

저는 React 공식 문서에서 추천하고 있고, 내부 동작보다는 간단하게 렌더된 결과만 테스트하고 싶어서 react-testing-library를 선택했습니다.

 

※ CRA (create-react-app)으로 시작한다면 jest는 자동적으로 설치되어 있습니다.

※ react-testing-library는 jest 프레임워크에서 많이 사용하지만, 꼭 jest와 같이 써야하는 것은 아닙니다

 

3. Jest를 이용한 자바스크립트 테스트

한 번 Jest를 설치해서 사용해볼까요?

만약 처음 프로젝트를 시작하는 것이라면 yarn init -y 혹은 npm init -y 를 통해 자바스크립트 프로젝트를 시작합니다.

 

(1) jest 설치

그리고 jest를 설치합니다.

yarn add jest // or npm install jest

 

※ 타입스크립트를  사용하신다면 코드 에디터의 지원을 받기 위해 @types/jest 를 설치하시는 것을 추천합니다.

yarn add --dev @types/jest

 

(2) 테스트 코드 작성

이제 예시 코드를 만들어보겠습니다.

간단하게 사칙연산의 일부를 담은 calculator.js 파일을 생성했습니다.

// calculator.js
function add(a, b) {
  return a + b;
}

module.exports = add;

 

그리고 해당 파일을 테스트하는 calculator.test.js 를 생성해보았습니다.

// calulator.test.js
const add = require("./calculator");

it("add correctly", () => {
  expect(add(3, 5)).toBe(8);
})

 

우선 it 이라는 것은 테스트 케이스를 만드는 함수입니다. test 라는 단어로 쓸 수도 있죠. 

그리고 expect는 테스트 통과 여부를 확인하기 위한 것으로 괄호 안의 값이 어떤 조건에 만족하는지 확인합니다. expect 뒤의 toBe는 그 조건으로, 값이 일치하는지, 이벤트가 실행됐는지 등등을 조건으로 하는 matcher 함수입니다.

즉, 해당 테스트 케이스는 add(3, 5)의 결과가 8과 '일치하는지'를 확인하는 테스트인 것입니다.

 

(3) 테스트 스크립트 실행

그리고 나서 테스트를 실행하기 위해 package.json에 다음과 같은 scripts를 추가합니다.

// package.json
{
  ...,
  "scripts": {
    "test": "jest"
  },
  ...
}

 

그 후, 방금 추가한 test 스크립트를 실행합니다.

yarn test // or npm run test

 

잘 통과되는 것을 볼 수 있죠?

만약 toBe에 7을 넣고 실패한다면 어떻게 될까요?

 

그럼 어떤 값때문에 실패했는지 보여줍니다!

 

(4) ES6 지원하도록

위에서 쓴 문법은 사실 CommonJS 문법입니다.

그런데 코드를 다음과 같이 ES6 이상 문법으로 작성하면 어떻게 될까요?

// calculator.js
export const add = (a, b) => {
  return a + b
}

// calculator.test.js
import add from "./calculator"

it("add correctly", () => {
  expect(add(3, 5)).toBe(7)
})

 

jest는 CommonJS 모듈 시스템을 사용하기 때문에 ES6 문법을 사용하면 에러가 나타납니다.

해결하기 위해서 우선 패키지 하나를 다운받겠습니다.

yarn add --dev @babel/preset-env // or npm install -D @babel/preset-env

 

그리고 바벨 설정을 해주셔야 하는데요,

바벨 설정 파일이 있다면 해당 파일에 추가하고 없다면 루트에 babel.config.js 를 만들어줍니다.

// babel.config.js
module.exports = {
  presets: ["@babel/preset-env"],
}

 

그리고 실행하면 잘 작동하는 것을 확인할 수 있습니다!

 

(5) 타입스크립트 지원하도록

그럼 이제 파일을 ts로 바꿔보겠습니다. (저는 원래 타입스크립트를 선호합니다)

// calculator.ts
export const add = (a: number, b: number) => {
  return a + b
}

// calculator.test.ts
import { add } from "./calculator"

it("add correctly", () => {
  expect(add(3, 5)).toBe(8)
})

 

그리고 다시 돌려보면 에러가 발생하죠

 

해결하기 위해서는 ES6 지원했던 방식과 유사합니다.

우선 패키지를 다운받습니다.

yarn add --dev @babel/preset-typescript // or npm install -D @babel/preset-typescript

 

그리고 바벨 파일의 preset에 추가를 합니다.

// babel.config.js
module.exports = {
  presets: ["@babel/preset-env", "@babel/preset-typescript"],
}

 

그럼 이제 타입스크립트도 지원이 되는 것을 확인할 수 있습니다!

 

 

4. react-testing-library를 이용한 React 컴포넌트 테스트

이제 리액트 컴포넌트 테스트를 해보겠습니다.

처음 프로젝트를 시작하는 분이라면 react 프로젝트를 생성해주시면 됩니다.

yarn create test-name // or npx create-react-app test-name

 

(1) react-testing-library 설치

우선 패키지를 설치합니다.

yarn add --dev @testing-library/jest-dom @testing-library/react
// or npm install -D @testing-library/jest-dom @testing-library/react

 

※ 기존에는 react-testing-library와 jest-dom 을 사용했는데 현재는 @testing-library/react와 @testing-library/jest-dom 으로 변경되었습니다.

 

그리고 src 폴더 (CRA가 아니라면 루트 디렉토리)에 setupTests.js 파일을 생성합니다.

// setupTests.js
import '@testing-library/jest-dom';

 

(2) 컴포넌트 및 테스트 작성

테스트를 해보기위한 컴포넌트를 만들어보겠습니다.

저는 이름과 mbti를 담은 Account라는 컴포넌트를 만들었습니다.

// Account.tsx
const Account = (props: { name: string; mbti: string }) => {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>{props.mbti}</p>
    </div>
  )
}

export default Account

 

그리고 해당 컴포넌트를 테스트하기 위한 Account.test.tsx 파일을 만들어볼게요.

import Account from "./Account"
import { render } from "@testing-library/react"

it("matches snapshot", () => {
  const utils = render(<Account name="호박너구리" mbti="ESFJ" />)
  expect(utils.container).toMatchSnapshot()
})

it("shows the props correctly", () => {
  const utils = render(<Account name="호박너구리" mbti="ESFJ" />)
  utils.getByText("호박너구리")
  utils.getByText("ABCD")
})

 

우선 toMatchSnapshot은 실행하면, 아래와 같은  __snapshots__/Account.test.tsx.snap 이라는 파일이 만들어집니다.

이는 이후 컴포넌트를 수정했을 때 원하는 방식으로 렌더되는지 비교하기 위함인데요,

업데이트 하고싶다면 테스트 중인 콘솔 창에서 u 를 누르면 됩니다!

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`matches snapshot 1`] = `
<div>
  <div>
    <h2>
      호박너구리
    </h2>
    <p>
      ESFJ
    </p>
  </div>
</div>
`;

 

그리고 하단의 getByText는 특정 문구를 가진 것이 있는지 확인하는 것인데,

Text 뿐만 아니라 title, altText 등도 사용할 수 있습니다.

저는 ESFJ를 넣었는데 ABCD가 있는지 확인해서 아래와 같이 에러가 뜨게 되는 것이죠!

 

이제 jest 와 react-testing-library 를 활용하여 React 유닛 테스트를 만들 수 있을 것입니다!

그럼 오늘도 즐거운 코딩하세요:)

 

※ 컴포넌트 테스트 중, css import 관련해서 에러가 발생한다면?

identity-obj-proxy 패키지를 설치해줍니다.

yarn add --dev identity-obj-proxy // or npm install -D identity-obj-proxy

 

그리고 루트 디렉토리에 jest.config.js 파일을 생성하고 다음과 같이 입력해줍니다.

module.exports = {
  moduleNameMapper: {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy",
  }
}

 

 

728x90

관련글 더보기

댓글 영역