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

최근 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을 사용할 경우 환경 구축은 매우 간단하다. 아니 할 게 없다.

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

$ yarn 

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

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

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

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’라는 텍스트를 포함한 요소가 있는지 확인했다.

테스트가 잘 동작한다.

이번에는 카운트를 올리고 내려줄 +, -버튼과 현재의 카운트를 보여줄 영역이 필요하다. 초기값을 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’이 보이지 않는다.

.

그럼 이제 이 실패한 테스트를 성공하도록 만들면 된다. 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해준다. 다음과 같이 작성하면 테스트는 다시

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

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

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

Github Source Code

woohyun