Front-End

[React-Testing-Library] 리액트 테스트 코드 작성하는 방법 및 느낀점

jaeyeong 2023. 5. 18. 14:42

개인 프로젝트를 진행하면서 새로 시도해 볼 것들을 몇 가지 정해두고 시작했다.

그중 하나는 테스트 코드를 작성하는 것이다.

처음 작성해 보면서 들었던 생각과 어떻게 코드를 작성했는지 기록을 남겨보려고 한다.

 

테스트 코드를 작성하기 전 길잡이

1. 어떤 코드를 작성할 것인지

사용자에게 입력 값을 받기 위한 form 컴포넌트가 필요하다고 생각해 보고, 이 코드를 화면에 그릴 예정이다.

 

원활한 테스트를 위해 더 구체적으로 정해보자면, 사용자에게 가보고 싶은 여행지를 답변받아야 한다고 할 때,

화면 상단에 '가보고 싶은 여행지는?' 이라는 질문이 있고 사용자는 그 아래 있는 input에 답을 작성한 다음

"제출" 버튼을 클릭하여 양식을 제출할 수 있다고 하자.

 

2. 어떤 코드를 테스트할 것인지(테스트 코드 시나리오)

시나리오를 그려봤을 때 꼭 필요한 UI와 발생할 수 있는 동작이 어떤 것인지 먼저 생각해봐야 한다.

나는 UI중엔 사용자의 입력을 받는 게 가장 중요하다고 생각되어서

input 요소와 제출 button 요소가 꼭 필요하다고 정했다.

 

그 다음 동작을 생각해 보면 다음과 같다.

 

  1. 사용자가 답변을 작성하지 않고 제출 버튼을 클릭했을 때 안내 메시지 (에러 메시지)
  2. 사용자가 답변을 작성하고 제출 버튼을 클릭했을 때 안내 메시지 (정상 처리)

그다음은 위에서 생각한 대로 요소들이 화면에 잘 렌더링 되는지,

사용자의 동작에 따라 원하는 피드백을 제공하고 있는지 테스트 코드로 작성하면 된다.


테스트 코드 작성 시작

1) 컴포넌트 렌더링시키기

페이지가 열리면 화면에 요소들이 잘 나오는지 확인하기 위해서는 컴포넌트를 렌더링 시켜야 한다.

렌더링을 위한 코드는 render() api를 사용하면 된다.

import { render, screen } from '@testing-library/react';

const renderComponent = () => {
  render(<EnterTravelDestination />);

  const input = screen.getByPlaceholderText('7월 22일 강원도 여행 숙소');
  const saveBtn = screen.getByText('저장');

  return {
    input,
    saveBtn,
  };
};

 

각 테스트마다 컴포넌트를 렌더링 한 뒤 input 요소와 button 요소를 가져와야 하는데 이때 코드 중복이 발생한다.

여러 줄의 코드가 반복적으로 작성되는 것을 방지하기 위해 위 코드처럼 렌더링 후 요소들을 가져오는 코드를 별도로 분리하였다.

 

여기서 getByPlaceholderText(값)는 tag의 속성 중 placeholder를 확인하여 값과 일치하는 요소를 가져오는 역할을 한다.

getByText(값)는 요소 중 값과 일치하는 요소를 가져오는 역할을 한다.

 

2) 렌더링 되었는지 확인하기

렌더링 되었는지 확인하는 방법은 가져온 input, button 요소가 null인지 아닌지 확인하는 것이다.

describe('여행지 입력 컴포넌트', () => {
  test('여행지 입력 컴포넌트가 렌더링이 되었나요?', () => {
    const { input, saveBtn } = renderComponent();
    
    expect(input).not.toBeNull();
    expect(saveBtn).not.toBeNull();
  });
}

 

expect(value)는 뒤에 오는 matcher 함수와 짝을 이뤄 사용되는데

위 코드에서 matcher 함수는 .not.toBeNull()이고, input, saveBtn 값이 null이 아님을 확인하고 있다.

만약 .not 없이 expect(value).toBeNull()로 사용된다면 value 값이 null임을 확인하는 것이다.

 

여기서 describe(), test() api가 사용된다.

test()테스트 코드를 실행하기 위해 사용되는 것인데,

첫번째 인자에는 테스트에 대한 설명을 작성하고, 두 번째 인자에는 실행할 테스트 코드를 작성하면 된다.

 

describe()테스트 코드를 그룹으로 묶기 위해 사용되는 것인데,

첫번째 인자에는 동일하게 테스트에 대한 설명을 작성하면 되고, 두 번째 인자에 실행할 테스트 코드들을 쭉 작성하면 된다.

 

즉, 테스트를 하기 위해 필요한 api는 test()이며 describe()은 꼭 쓰지 않아도 된다.

하지만 테스트 코드가 많아질 땐 가독성을 위해 describe()을 사용하여 테스트 코드들을 그룹화하는 것이 좋아 보인다.

 

3) input 작성을 하지 않고 제출 버튼 누르기

이번 테스트를 실행하기 위해서는 버튼을 클릭하는 이벤트가 발생해야 한다.

클릭 이벤트가 일어나게 하기 위해서는 userEvent api를 사용하면 된다.

import userEvent from '@testing-library/user-event';

describe('여행지 입력 컴포넌트', () => {
  ...
  test('여행지를 작성하지 않고 저장버튼을 눌렀을 때, 오류 메시지가 안내된다.', async () => {
    const { saveBtn } = renderComponent();

    await userEvent.click(saveBtn);
    expect(screen.queryByText('여행지를 입력해주세요.')).toHaveAttribute('data-validate', 'false');
  });
}

 

userEvent.click(value)를 실행시키면 value가 클릭된다.

 

나는 '여행지를 입력해 주세요.'라고 쓰여있는 안내 메시지 태그에 data-validate 속성을 작성해 두고

버튼을 클릭했을 때 input에 유효한 값이 입력되어 있다면 true, 유효하지 않다면 false가 오도록 코드를 작성하려고 한다.

 

이를 위해 속성을 가지고 있는지 확인하는 .toHaveAttribute(속성, 값) matcher 함수를 사용하여 테스트 코드를 작성했다.

 

만약 버튼을 클릭했을 때 화면에 '여행지를 입력해 주세요.' 텍스트를 가지고 있는 요소를 가져오고,

이 요소의 data-validate 속성이 false라면  테스트는 통과하고, false가 아니라면 테스트는 실패하게 된다.

 

4) input 작성한 뒤 제출 버튼 누르기

이번 테스트를 실행하기 위해서는 input에 값을 입력한 뒤 버튼을 클릭하는 이벤트가 발생해야 한다.

입력 이벤트 역시 userEvent api를 사용하면 된다.

import userEvent from '@testing-library/user-event';

describe('여행지 입력 컴포넌트', async () => {
  ...
  test('여행지를 작성하고 저장버튼을 눌렀을 때, 저장에 성공한다.', () => {
    const { input, saveBtn } = renderComponent();

    await userEvent.type(input, '여행지 이름 작성');
    await userEvent.click(saveBtn);
    expect(screen.queryByText('여행지를 입력해주세요.')).toHaveAttribute('data-validate', 'true');
  });
}

 

userEvent.type(value, 값)를 실행시키면 value에 값이 작성된다.

(value에는 input, textarea가 올 수 있다.)

 

요소들이 렌더링 되고 input에 여행지 이름을 작성 후 버튼을 클릭했을 때

'여행지를 입력해주세요.' 텍스트를 가지고 있는 요소를 가져오고,

이 요소의 data-validate 속성이 true라면  테스트는 통과하고, true가 아니라면 테스트는 실패하게 된다.


느낀 점

TDD 즉, 테스트 주도 개발을 적용한다면 처음엔 실패하는 테스트 코드를 작성하라고 한다.

본격적인 코드를 작성하기 전에 내가 원하는 동작 흐름, 예외 상황 등을 미리 생각하면서 더 효율적으로 코드를 작성할 수 있다고 들어서 한 번 적용을 해보았고 확실히 효과가 있었다.

 

개발부터 시작했을 때 머릿속으로 생각한 동작들만 생각하고 코드를 작성하다가 갑자기 튀어나오는 에러와 예외 상황들이 당황스러웠고,

정말 최악의 경우엔 코드를 다시 작성했던 적이 있었다.

 

그래서 테스트 코드를 먼저 작성하는 과정을 통해 예외 상황을 생각해 볼 수 있는 것이 좋았고,

특히 내가 작성하고 있는 코드가 의도적으로 동작하는지 수시로 빠르게 검증할 수 있다는 점이 가장 좋았다.