React Testing Library로 TDD개발환경 구축하기

Image for post
Image for post

최근 TDD(테스트 주도 개발)과 테스트 케이스 작성하는 일의 중요성을 강조하는 책을 읽으면서 그 필요성이 충분히 공감되었다. 하지만 백앤드 개발환경에서 유닛테스트 위주로 테스트를 경험해본 나로서는 구체적으로 프론트앤드에서 어떻게 테스트를 하는지 막연한 상황이었다.

그렇게 Storybook이라는 라이브러리를 통해 독립적인 컴포넌트 단위의 개발 및 Snapshot test의 방법도 찾아보았고

이번에는 React-testing-library를 기반으로 TDD개발을 시도해보려고 한다.

React Testing Library는 Jest를 기반으로 UI테스트를 지원해주는 DOM Testing Library에 React Component를 위한 API들이 추가된 것이다.

getBy* 등의 query를 통해 요소를 찾고 이벤트를 발생(fireEvent)시키고 expect function을 통해 예측한 결과대로 이루어지는지 확인한다.

create-react-app을 사용할 경우 환경 구축은 매우 간단하다. 아니 할 게 없다.

Image for post
Image for post

create-react-app을 통해 앱을 생성하면 다음과 같이 이미 testing-library가 설치되어있다. 테스트 실행 스크립트 역시 설정되어있어서

$ yarn 

명령어를 통해 곧바로 실행할 수 있다.

Image for post
Image for post

Jest는 기본적으로 수정된 파일과 연관되어있는 test만 실행하는것 같다. 현재는 앱을 생성하고 아무런 수정사항 없이 test를 실행했기 때문에 다음과 같은 메세지가 출력된다.

프로젝트에 기본적으로 작성되어있는 ‘App.test.js’파일을 실행시키기 위해서는 여기서 a key를 눌러 모든 테스트를 실행시키거나, ‘App.tsx’파일을 수정한 이후 다시 테스트를 실행하면 된다.

Image for post
Image for post

App.test.js가 실행되면 다음과 같이 1개의 테스트가 성공됨을 알 수 있다.

CRA가 기본적으로 만들어주는 App.test.tsx파일을 보면

test('renders learn react link', () => {    const { getByText } = render(<App />);    const linkElement = getByText(/learn react/i);    expect(linkElement).toBeInTheDocument();});

App component를 render하고 getByText로 ‘learn react’라는 텍스트를 가지고 있는 dom element를 찾은 후, 해당 요소가 document에 존재하는지를 테스트했다.

App.tsx파일로 가서 ‘Learn React’텍스트가 있는 a tag의 내용을 ‘Learn Vue’로 수정해보자.

테스트는 실패할 것이고 다음과 같은 메세지가 나타날 것이다.

Unable to find an element with the text: /learn react/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

그렇다면 이제 TDD방법으로 가벼운 카운터앱을 만들어보자.

먼저 해당 컴포넌트 로직이 작성될 Counter.tsx파일과 테스트를 위한Counter.test.tsx파일을 생성하고 테스트가 잘 동작하는지 확인한다.

App.test.tsx와 같이 Counter component에 ‘counter’라는 텍스트를 포함한 요소가 있는지 확인했다.

Image for post
Image for post
테스트가 잘 동작한다.

이번에는 카운트를 올리고 내려줄 +, -버튼과 현재의 카운트를 보여줄 영역이 필요하다. 초기값을 0으로 시작한다고 하면 다음과 같이 코드를 작성할 수 있다.

// Counter.test.tsx...
test('check init count', () => {
const { getByText } = render(<Counter />);
// '0'이 있는지
const countElement = getByText( '0' ); expect(countElement).toBeInTheDocument();
// '+'버튼이 있는지
const plusElement = getByText( '+' ); expect(plusElement).toBeInTheDocument();
// '-'버튼이 있는지 const minusElement = getByText( '-' ); expect(minusElement).toBeInTheDocument();});

다음과 같이 테스트를 작성하면 결과는 당연히 실패. 우리는 아직 Counter component에 제목밖에 붙여주지 않았기에 ‘0’이 보이지 않는다.

Image for post
Image for post
.

그럼 이제 이 실패한 테스트를 성공하도록 만들면 된다. Counter 앱에 초기값을 0으로 하는 state를 만들어서 렌더해주고, 숫자의 앞뒤로 +,- 버튼을 만들었다.

// Counter.tsx...function Counter() {    const [ count, setCount ] = useState<number>( 0 );    return (        <div className="counter">            <h1>I'm counter</h1>            <button className="plus-button">-</button>            <p className="count">{ count }</p>            <button className="minus-button">+</button>        </div>    );}...

테스트가 성공적으로 pass된다!

이번에는 plus, minus 기능을 만들어 볼것이다.

test('check init count', () => {    const { getByText } = render(<Counter />);
// +버튼 클릭
const plusElement = getByText( '+' ); fireEvent.click( plusElement );
// 1이 있는지
const countElement = getByText( '1' ); expect( countElement ).toBeInTheDocument(); // -버튼 클릭
const minusElement = getByText( '-' );
fireEvent.click( minusElement );
// 0이 있는지
const countElement2 = getByText( '0' ); expect( countElement2 ).toBeInTheDocument();});

+ 버튼을 클릭하고 ‘1’텍스트를 가지고 있는 요소가 있는지 확인한다. 그 후에는 -버튼을 눌러보고 다시 0으로 돌아왔는지를 확인한다. 결과는 당연히 실패. 이제 count state를 올려주는 기능을 만들어보겠다.

onClickPlus와 onClickMinus는 useCallback으로 count가 갱신 될 때마다 재정의되는 함수로, 클릭하면 count state를 각각 +1, -1해준다. 다음과 같이 작성하면 테스트는 다시

Image for post
Image for post

성공하게 되고, 이제 Counter component를 App에서 렌더링해주도록 수정한 후 해당 앱을 실행시키면

Image for post
Image for post

정상적으로 작동하는 앱을 확인할 수 있다!

이렇게 아주 작은 기능이지만 React Testing Library를 통해 React앱을 테스트케이스부터 만들어서 개발해보았다. 아직은 아주 기본적인 기능밖에 시도해보지 않았지만 더 공부하면 복잡한 앱을 만들때도 처음 개발 단계에서부터 훨씬 잘 정리되고 안정성있는 기능을 만드는데 유용하게 활용할 수 있을것이라 기대한다.

Github Source Code

woohyun

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store