React 테스트 코드 (feat. jest, React Testing Library)
React Testing Library | Testing Library
CRA와 함께 사용하는 Jest
React 프로젝트를 생성하는 방법은 생략하도록 하겠습니다.
create-react-app
을 사용하여 프로젝트를 생성 했을 때 기본적인 Jest 라이브러리가 같이 설치가 됩니다.따라서 기본적인 테스트 코드를 위한 라이브러리 설치는 필요없습니다. 여기서는 렌더링 스냅샷을 사용하기 위해 react-test-renderer
을 추가 설치하겠습니다.
yarn add --dev react-test-renderer
//Link.tsx
import {useState} from 'react';
const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};
export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);
const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};
const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};
return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
}
// Link.test.tsx
import renderer from 'react-test-renderer';
import Link from '../Link';
it('changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
테스트 실행 yarn test
를 실행하게 될 경우 테스트가 실행되고 toMatchSnapshot()
을 만나 아래와 같은 스냅샷 파일이 만들어지게 됩니다. 이를 이용하여 테스트가 실행되는 도중에 컴포넌트가 어떻게 생겼는지 확인할 수 있습니다.
Enzyme vs React Testing Library 비교
React에서 사용하는 테스트 라이브러리를 대표적으로 Enzyme과 React Testing Library(이하 RTL)이 있습니다.
아래 이미지는 최근 5년간 두 라이브러리에 npm 다운로드 수를 비교한 그래프입니다. 현재는 RTL이 압도적으로 높은 경향이 있습니다. 코드를 작성하기 앞서 간단하게 두 라이브러리에 차이를 비교해보겠습니다.
- Enzyme
Enzyme은 2015년도에 출시된 AirBnb에서 개발한 테스팅 도구입니다. Enzyme는 공식적으로 React 16V까지만 지원하고 있습니다. 하지만 커뮤니티에서 최신 버전에 맞는 어댑터를 만들어 사용할 수 있습니다. 또한 얕게 렌더링, 전체 DOM 렌더링, 정적 렌더링의 세 가지 방법으로 React 컴포넌트를 렌더링할 수 있습니다.
- React Testing Library
공식적으로 React 18V까지 지원하고 있어 별도에 어댑터 없이 사용할 수 있습니다. React-dom 및 React-dom/test-utils에 대한 가벼운 유틸리티 함수를 제공하며, "mount"와 동등한 "render"만 사용하여 React 컴포넌트를 렌더링합니다.
둘의 차이로는 Enzyme이 Implementation Driven Test(구현 주도 테스트) 라는 목표에 따라 어플리케이션이 내부적으로 어떻게 동작하는지에 초점을 맞췄다면, RTL은 Behavior Driven Test(행위 주도 테스트) 라는 목표 아래에서 실제 사용자의 행위와 사용자가 보는 화면을 테스트하는데 초점을 맞췄습니다.
뭐가 더 좋은 테스팅 도구라고 말할 수 없을 것 같습니다. 개발자 성향, 프로젝트 성격 등에 따라 적절한 선택이 달라지겠지만,별다른 이유가 없다면 다운로드 수가 많고 React에서 공식적으로 권장하는 RTL이 좋은 선택인 것 같은 생각이 듭니다.
따라서 아래에서는 RTL내용만 다루도록 하겠습니다.
React Testing Library
- 라이브러리 설치
- CRA로 프로젝트를 생성했다면 기본적으로 모든 라이브러리가 설치되어 있습니다. 최신 버전을 원한다면 다시 설치하면 되겠습니다.
// CRA로 프로젝트 생성 시 자동설치
yarn add --dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
- 컴포넌트 작성
import React, { useState } from "react";
const TestTitle = (props: { title: string }) => {
const [title, setTitle] = useState<string>("prev Title");
const changeTitle = (newTitle: string) => {
setTitle(newTitle);
};
return (
<>
<div className="title">{title}</div>
<button
className="button"
onClick={() => {
changeTitle(props.title);
}}
>
change Title
</button>
</>
);
};
export default TestTitle;
위 컴포넌트는 버튼을 클릭 시 props로 전달된 Title로 값을 변경하는 기능을 담고있습니다.
이 컴포넌트를 기반으로 버튼 클릭 시 예상대로 UI가 변경되는지 테스트 코드를 작성해보겠습니다.
- 테스트 코드 작성
import { render, screen } from "@testing-library/react";
import Counter from "../View/Counter";
import userEvent from "@testing-library/user-event";
test("버튼을 누르면 제목이 바뀐다.", () => {
render(<TestTitle title="hello world" />);
const changeButton = screen.getByText("change Title");
userEvent.click(changeButton);
expect(document.querySelector(".title").textContent).toBe("??");
});
위 코드를 살펴보면 TestTitle
컴포넌트에 “hello world” props를 전달하여 렌더링 후 change Title을 text로 가지고있는 태그를 click하는 테스트 코드입니다. 테스트 결과를 확인하기 위해 일부로 실패되도록 작성했습니다.
- 테스트 실행 (
yarn test
)
기대값은 props로 전달한 “hello world”이며, Received 부분을 보면 'hello world'가 제대로 렌더링 되는 것을 확인했습니다. ?? 대신 hello world로 수정한다면 테스트가 통과되는 것을 볼 수 있습니다
마무리
아주 간단한 react dom 관련된 테스트 코드를 작성해봤습니다. 테스트 코드 작성시 사용하는 라이브러리, 방식은 다양하지만 오늘 기준으로 가장 많이 사용하는 방법은 RTL
을 사용하는 방법입니다.
오늘 예시는 간단한 코드를 작성했기 때문에 이것을 보고 실용성이 없다고 생각할 수 있습니다.
테스트 코드를 작성하므로 생기는 이점은 다양하겠지만, 제가 생각하는 중요한 이점은 회귀 테스트인 것 같습니다. UI가 바뀌는 경계값을 기준으로 작성해두면 컴포넌트 확장, 수정에 있어 기존 기능이 정상 동작하는지 확인 할 수 있기 때문입니다.
다음 포스트에서는 TodoList 프로젝트를 만들어보면서 테스트 코드도 작성해보도록하겠습니다.
ps. 아직 실무에서 테스트 코드를 작성해본적 없는 코린이입니다..... 코딩을 잘하던 못하던 피드백은 소중이 듣고있습니다. 혼자 삽질하는 절 위해 피드백이 보인다면 거침없이 작성해주시면 감사하겠습니다.