모던 프론트엔드 테스트 전략 — 2편(Testing React Component)

Eunsu Kim
Team QANDA
Published in
8 min readJul 1, 2021

--

@testing-library/react Github repository introduction
@testing-library/react Github repository introduction

지난 포스팅에서는 프론트엔드 테스트 환경, 테스트 대상과 테스트 도구들에는 어떤 것들이 있는지 알아보았습니다. 이번 포스팅에서는 Node.js환경에서 Jest, Testing-library를 활용해서 어떻게 프론트엔드 테스트 코드를 작성할 수 있는지 알아보겠습니다.

Note: 이 포스팅에서는 Jest를 테스트 러너로 사용합니다. Jest 설치 및 설정에 대해서는 다루지 않으니 이와 관련해서는 공식 도큐먼트를 참고해 주세요.

Testing React — basic

Testing-library를 활용하기에 앞서 React 공식 문서에서 제안하는 테스트 방법을 먼저 살펴보겠습니다.

Setup/Teardown

먼저 테스트 하고자 하는 컴포넌트를 렌더링하기 전에, 다음과 같이 컴포넌트를 마운트할 루트 컨테이너(container) DOM element를 생성해야 합니다. 또 각 테스트가 종료되면 해당 컨테이너를 초기화하여 다음 테스트에 영향을 미치지 않도록 해야 합니다.

Rendering

React는 컴포넌트 렌더링을 비롯한 유저 이벤트, 데이터 fetch 등의 UI 상호작용을 하나의 ‘단위’로 보면서 이와 관련된 변경사항들이 단언(assertion)을 하기 전에 모두 처리되어 DOM에 적용될 수 있도록 도와주는 헬퍼 함수인 act() 메서드를 제공하고 있습니다.

그리하여 다음과 같이 컴포넌트를 렌더링하는 로직을 act()로 래핑한 뒤 assertion을 수행하여 테스트를 작성할 수 있습니다.

act(() => {
// 컴포넌트를 렌더링 합니다.
});
// act 이후에 단언을 수행합니다.

이해를 돕기 위해 다음과 같이 간단한 Counter 컴포넌트가 있다고 생각해봅시다.

이 카운터 컴포넌트를 테스트하기 위해 다음과 같이 테스트 코드를 작성할 수 있습니다. 먼저 setup/teardown 로직을 작성하고 컴포넌트를 렌더링하는 로직을 act() 메서드로 감싸서 렌더링이 완료된 뒤에 텍스트나 버튼이 DOM 트리에 존재하는지 assertion 할 수 있습니다.

위 테스트 코드를 실행하면 PASS 하는 것을 확인할 수 있습니다. 그러나 위 테스트 코드는 잘 짜인 테스트 코드라고 말하기는 어렵습니다. 그 이유는 먼저 setup/teardown 하는 보일러 플레이트가 존재하기 때문입니다. 지금은 단 하나의 컴포넌트를 테스트하기 때문에 문제 되지 않지만, 여러 컴포넌트를 테스트하게 된다면 각 테스트 코드마다 setup/teardown 로직이 중복해서 나타나게 됩니다. DRY 원칙에 위배되지 않기 위해선 개선되어야 할 부분입니다.

또 다른 이유로 현재 DOM element를 찾기 위해 .querySelector와 같은 DOM API를 활용하고 있는데, 이러한 방법은 실제로 애플리케이션 사용자가 애플리케이션을 사용하는 방법과는 다소 거리가 있습니다. 나아가 개발자가 볼 때 가독성도 떨어져서 테스트 코드를 작성한 개발자의 의도를 명확하게 파악하기 힘듭니다.

개발자가 자동화된 테스트코드를 작성하는 목적은 애플리케이션이 예상하는 대로 동작한다는 것에 대한 신뢰를 얻기 위함이라고 이전 포스팅에서 말씀드렸습니다. 테스트 코드가 실제 사용자에 의해 애플리케이션이 사용되는 방식과 닮을수록 개발자는 더 많은 신뢰를 얻을 수 있습니다. 그러면 위와 같이 작성된 테스트 코드를 어떻게 개선할 수 있을까요?

Testing React — with @Testing-library/dom

이 문제를 해결하기 위해 Testing-library가 만들어졌습니다. Testing-library는 마치 사용자가 실제로 애플리케이션을 사용하는 것과 같이 테스트 코드를 작성할 수 있도록 여러 유틸리티를 제공해줍니다. 나아가 특정 라이브러리나 프레임워크에 종속되지 않고 테스트 할 수 있도록 @testing-library/dom 패키지를 제공하고, React, Vue, Angular 등 여러 라이브러리나 프레임워크에 맞게 wrapping 한 패키지도 제공하고 있습니다.

먼저 위에서 작성한 Counter 컴포넌트 테스트 코드를 @testing-library/dom을 활용하여 다음과 같이 작성할 수 있습니다.

Note: Testing library는 페이지에서 특정 DOM element를 찾는 방법을 제공하며 이를 ‘쿼리(Queries)’라고 합니다. 쿼리의 종류는 element를 찾지 못 하였을 때와 retry 여부에 따라 크게 3가지(getBy..., queryBy..., findBy...)로 나누어지는데, 쿼리에 대한 자세한 내용은 공식 문서를 참고하세요.

위 코드에서와같이 getQueriesForElement 라는 함수의 파라미터로 container를 전달하면 container에 마운팅된 컴포넌트에 포함된 DOM element를 찾는 쿼리(getByText)들을 반환합니다(source code). getBytext 쿼리는 DOM tree에서 주어진 텍스트와 일치하는 element를 찾고, 존재하면 해당 element를 반환하고 그렇지 않으면 error를 throw 하여 테스트를 실패하게 합니다.

위 테스트 코드가 이전에 작성한 테스트 코드와 어떻게 다른지 비교해보면, DOM element에 접근하는 방식(getByText)이 마치 사용자가 웹 사이트에 접속해서 텍스트를 읽는 행위와 유사합니다. 즉, 애플리케이션이 실제로 사용되는 방식으로 테스트 코드가 작성되었습니다. 나아가 쿼리의 이름만 보고도 무슨 일을 하는지 예상할 수 있고, 테스트 코드의 의도를 명확하게 파악할 수 있게 바뀌었습니다.

Keep DRY principle

그러나 위 테스트 코드에는 여전히 setup/teardown 로직이 남아있습니다. 이 로직이 다른 테스트 코드에서 중복되지 않도록 하기 위해 다음과 같이beforeEach 훅에서 컴포넌트를 마운트할 컨테이너를 생성하는 로직을 render()라는 함수로 추출할 수 있습니다. 또한 afterEach 훅에서 컨테이너에 마운트된 컴포넌트를 언마운트 시키고 컨테이너를 DOM tree에서 제거하는 로직을 cleanup() 함수로 추출할 수 있습니다.

이렇게 render(), cleanup() 함수를 만들면 기능을 유지하면서도 다른 테스트 코드에서 해당 함수를 재사용할 수있기 때문에 setup/teardown로직의 중복을 피할 수 있습니다.

Testing React — with @Testing-library/react

위에서 만든 render(), cleanup() 함수와 비슷한 기능을 하는 유틸리티를 @testing-library/react 패키지에서 제공하고 있습니다. 그리하여 위에서 @testing-library/dom으로 작성한 테스트 코드를 @testing-library/react를 활용해서 작성해보면 다음과 같습니다.

어떤가요? 테스트 코드가 눈에 띄게 줄어들었습니다. 이는 @testing-library/react에서 제공하는 render 함수가 내부적으로 setup 로직을 수행하고, global context에서 teardown을 처리해주기 때문입니다. 또한 내부적으로 React의 act() 함수를 wrapping 하므로 컴포넌트 렌더링 등의 UI 상호작용 시 명시적으로 act() 함수를 호출할 필요가 없습니다. 그리하여 React 애플리케이션을 테스트할 때 @testing-library/react를 활용하면 불필요한 코드의 중복을 피하고 사용자 입장에서 테스트 코드를 작성할 수 있습니다.

Conclusion

이번 포스팅에서는 React 컴포넌트를 테스트하는 방법에 대해 살펴보았습니다. React에서 제공하는 테스트 유틸리티만 활용한 전통적인 방법부터 @testing-library/dom, @testing-library/react를 활용한 방법까지 알아보았는데요, 이번 포스팅에서 소개한 내용을 정리하면 다음과 같습니다.

  • React 컴포넌트를 테스트할 때 각 테스트가 독립적으로 실행되고 다른 테스트에 영향을 미치지 않도록 하기 위해 setup/teardown하는 로직이 필요합니다.
  • React는 테스트 코드에서 컴포넌트 렌더링, 사용자 이벤트, 데이터 페치 등 UI 상호작용으로 인한 DOM의 변경이 assertion 전에 완료되도록 하는 act() 함수를 제공합니다.
  • @testing-library/dom 패키지를 활용하면 마치 사용자가 애플리케이션을 사용하는 것처럼 테스트 코드를 작성할 수 있고, 그로 인해 작성자의 의도를 명확하게 이해할 수 있으며 궁극적으로 테스트가 PASS 할 때 개발자는 더 많은 신뢰를 얻을 수 있습니다.
  • @testing-library/react 패키지를 활용하면 중복되는 코드(setup/teardown, act, etc.)의 작성을 피할 수 있습니다.

🌏글로벌 Top 교육 앱 QANDA(콴다)를 함께 만들어 갈 분들을 기다리고 있습니다! 자세한 사항은 ➡️ 채용 사이트 ⬅ ️를 통해 확인해주세요.

--

--