프론트엔드 TDD 튜토리얼 with React & Testing Library

Testing Library를 활용해 리액트 컴포넌트를 TDD로 개발하는 방법을 소개합니다

2024-03-03에 씀
프론트엔드 테스트 가이드 시리즈의 다른 글
  1. jest로 비동기 함수 테스트하기
  2. Storybook Interaction Test를 활용한 바텀시트 시각적 테스트
  3. React Testing Library로 테스트할 요소를 선택하는 방법
  4. 프론트엔드 TDD 튜토리얼 with React & Testing Library

테스트를 작성해 보기 위해, 블로그 서비스를 개발하고 있는 상황을 가정해 보겠습니다. 지금 만들고 있는 컴포넌트는 아래와 같은 '포스트 카드' 컴포넌트입니다.

이 컴포넌트 안에서도 특히 기능과 정책이 포함된 영역이 있습니다. 바로 하단에 있는 해시태그 영역입니다.

이 영역에 대한 정책은 아래와 같습니다.

위의 정책을 토대로, '해시태그 영역' 컴포넌트를 TDD 기반으로 구현해 보려고 합니다. 이 컴포넌트의 이름은 HashtagList라고 부르겠습니다.

🤔 무엇을 테스트할까?

TDD를 하려면 먼저 테스트를 작성해야 하는데, 구현된 코드가 없으니 어떤 부분을 테스트해야 할지 모르겠네요. 일단 저희가 알고 있는 것은 요구사항뿐이니 요구사항을 기반으로 사용자 시나리오를 작성해 보겠습니다.

혹시 사용자 시나리오를 작성하기 어렵다면, 아래 내용을 생각해 보세요.

위 내용을 고려하여, ‘해시태그 영역’에 대한 사용자 시나리오를 아래와 같이 작성해 봤습니다.

1- 사용자가 처음으로 해시태그 영역을 보게 되면
2 - 포스트의 해시태그가 5개 이하일 경우
3 - 모든 해시태그가 표시된다
4 - ‘더보기’ 버튼이 노출되지 않는다
5 - 포스트의 해시태그가 5개보다 많을 때
6 - 해시태그가 5개까지 노출된다
7 - ‘더보기’ 버튼이 노출된다
8- 사용자가 해시태그를 클릭하면
9 - 검색어에 해당 해시태그 이름이 설정된다
10- 사용자가 해시태그에 마우스를 올리면
11 - 해시태그에 밑줄이 생기고, 글자색과 배경색이 연해진다
12- 사용자가 ‘더보기’ 버튼을 누르면
13 - ‘더보기’ 버튼이 노출되지 않는다
14 - ‘접기’ 버튼이 노출된다
15 - 포스트의 해시태그가 10개 이상일 경우
16 - 해시태그가 10개까지만 노출된다
17 - 포스트의 해시태그가 5개 이상, 10개 이하일 경우
18 - 해시태그가 모두 노출된다
19- 사용자가 ‘접기’ 버튼을 누르면
20 - ‘더보기’ 버튼을 누르기 이전 상태로 돌아간다

사용자 시나리오를 계층화하기 위해 describe-context-it 패턴을 활용할 수도 있습니다.

계층역할예시
describe테스트할 대상HashtagList 컴포넌트에서
context대상이 놓인 상황, 환경사용자가 ‘더보기’ 버튼을 눌렀을 때, 포스트의 해시태그가 10개 이상일 경우
it기대 결과해시태그가 10개까지만 노출된다

이렇게 계층화하면 시나리오를 파악하기 편하고, 나중에 테스트 코드로 변환했을 때에도 각 테스트의 선행 조건 등을 관리하기 편합니다.


그런데, 방금 작성한 모든 시나리오를 단위 테스트의 테스트 케이스로 사용할 필요는 없습니다. 예를 들어, 해시태그에 밑줄이 생기고, 글자색과 배경색이 연해진다 같은 테스트는 단위 테스트로 검증하기 보다는 스토리북을 활용해서 확인하는 게 더 나을 수도 있습니다.

Testing Library로는 기능적인 부분을 테스트하고, 시각적인 부분을 테스트하는 건 스토리북에 렌더링 된 실제 UI를 체크하는 것이 더 효과적입니다.


이제 테스트를 생성해 보겠습니다. 이때, 테스트의 내용은 작성하지 않고 이름만 작성하겠습니다.

공통되는 조건이나 상황을 describe로 묶고, 이에 대한 기대 결과를 test로 작성했습니다. 아직 테스트의 내용을 작성하지 않았기 때문에, test.todo를 사용해 테스트의 이름만 명시했습니다.

💣 실패하는 테스트 작성하기

테스트를 작성하기 위해서 빈 컴포넌트를 먼저 만들어 두면 편합니다. Fragment 만을 반환하는, 아무런 UI도, 로직도 가지고 있지 않은 아주 기본적인 컴포넌트를 만들었습니다. 이 컴포넌트가 어떤 속성과 로직을 가질지는 테스트를 작성하는 과정에서 결정됩니다.

이 컴포넌트를 가지고 포스트의 해시태그가 5개보다 많을 경우, 해시태그가 5개까지 표시된다에 대한 테스트를 작성해 보겠습니다.

1describe("포스트의 해시태그가 5개보다 많을 경우", () => {
2 test("해시태그가 5개까지 표시된다", () => {
3 const hashtags = [
4 "해시태그 1",
5 "해시태그 2",
6 "해시태그 3",
7 "해시태그 4",
8 "해시태그 5",
9 "해시태그 6",
10 ];
11 render(<HashtagList />);
12
13 const listitems = screen.getAllByRole("listitem");
14
15 expect(listitems).toHaveLength(5);
16 expect(listitems[0]).toHaveTextContent("해시태그 1");
17 expect(listitems[1]).toHaveTextContent("해시태그 2");
18 expect(listitems[2]).toHaveTextContent("해시태그 3");
19 expect(listitems[3]).toHaveTextContent("해시태그 4");
20 expect(listitems[4]).toHaveTextContent("해시태그 5");
21
22 expect(screen.queryByText("해시태그 6")).not.toBeInTheDocument();
23 });
24});

테스트를 실행해 보면, 잘 실패하는 것을 확인할 수 있습니다. ‘listitem’이란 역할의 element를 찾지 못했다고 하네요.

🩹 테스트를 성공시키기

1describe("포스트의 해시태그가 5개보다 많을 경우", () => {
2 test("해시태그가 5개까지 표시된다", () => {
3 const hashtags = [
4 "해시태그 1",
5 "해시태그 2",
6 "해시태그 3",
7 "해시태그 4",
8 "해시태그 5",
9 "해시태그 6",
10 ];
11 render(<HashtagList />);
12
13 const listitems = screen.getAllByRole("listitem");
14
15 expect(listitems).toHaveLength(5);
16 expect(listitems[0]).toHaveTextContent("해시태그 1");
17 expect(listitems[1]).toHaveTextContent("해시태그 2");
18 expect(listitems[2]).toHaveTextContent("해시태그 3");
19 expect(listitems[3]).toHaveTextContent("해시태그 4");
20 expect(listitems[4]).toHaveTextContent("해시태그 5");
21
22 expect(screen.queryByText("해시태그 6")).not.toBeInTheDocument();
23 });
24});

이 테스트를 성공시키기 위해서는 컴포넌트를 수정해야 합니다.

  1. property로 해시태그 리스트를 받아오기
  2. 해시태그 리스트를 listitem role을 가지는 element로 렌더링하기

먼저 컴포넌트를 수정해 주겠습니다.

테스트에서 HashtagList 컴포넌트에 hashtagList 속성을 넘겨주게 변경하면, listitem 역할의 요소가 6개 존재하기 때문에 실패합니다.

화면에 보여줄 해시태그 리스트 배열을 따로 관리하도록 수정했습니다.

1export const HashtagList = ({ hashtagList }: Props) => {
2 const visibleHashtagList = hashtagList.slice(0, 5);
3 return (
4 <ul>
5 {visibleHashtagList.map((hashtag) => (
6 <li key={hashtag}>{hashtag}</li>
7 ))}
8 </ul>
9 );
10};

다시 테스트를 실행해 보면, 방금 작성한 테스트를 통과했습니다! 🎉

🔧 리팩토링하기

그 밖의 다른 테스트는 따로 작성해서, 모든 테스트를 통과시키는 컴포넌트를 완성했습니다! 아래 캡쳐는 모든 테스트를 통과시키는 HashtagList 컴포넌트가 실제로 렌더링 된 모습입니다.

확실히 디자인 팀에서 원한 모습은 이게 아닐 것 같네요. 그렇지만 테스트를 작성해 뒀기 때문에, 기능의 동작을 보장하면서 안전하게 UI를 수정할 수 있습니다.

🧚 테스트 100% 활용하기

코드를 유지보수하는 것처럼, 테스트도 꾸준히 유지보수하면 견고하고 안정적인 프로젝트를 만드는 데 큰 도움이 될 수 있습니다.

👏 마치며

기능 개발 초기에 테스트를 작성하지 않으면, 개발이 진행될수록 테스트를 작성하기 어렵고 귀찮아져서 결국에는 무시되는 경우가 많습니다. 그렇기 때문에 TDD로 개발하는 습관을 들여 두면 좋습니다.

프론트엔드에서 TDD로 테스트 코드를 작성하게 되면 이런 점이 좋습니다.

아직 테스트 작성에 익숙하지 않으시다면, 본문과같이 간단한 컴포넌트에 대한 테스트를 작성하는 것부터 시작해 보면 좋습니다. 이번 글에서는 간단한 컴포넌트에 대한 테스트를 작성해 봤지만, 복잡하고 큰 컴포넌트나 훅도 본문에서 소개한 방법을 토대로 충분히 테스트를 작성해 볼 수 있습니다.

프론트엔드 테스트 가이드 시리즈의 다른 글
  1. jest로 비동기 함수 테스트하기
  2. Storybook Interaction Test를 활용한 바텀시트 시각적 테스트
  3. React Testing Library로 테스트할 요소를 선택하는 방법
  4. 프론트엔드 TDD 튜토리얼 with React & Testing Library
프로필 사진

조예진

이전 포스트
React Testing Library로 테스트할 요소를 선택하는 방법
다음 포스트
개발 블로그 개발 여정 (feat. Next.js & MDX)