[Testing Library #2] React-testing-library (RTL) 시작해 보기

Janet·2024년 5월 5일
0

Web Development

목록 보기
16/19
post-thumbnail

1. React Testing Library(RTL)이란?

  • React Testing Library는 React 컴포넌트의 테스트를 더 쉽고 직관적으로 작성할 수 있도록 도와주는 라이브러리이다.
  • CRA(Create-React-App)로 프로젝트를 생성하면 기본적으로 포함되어 있는 테스팅 라이브러리이다.
  • 컴포넌트의 내부 구현보다는 사용자가 실제로 상호작용하는 방식에 초점을 맞추어 테스트를 작성한다. 그래서 실제 브라우저에서 보여지는 DOM을 기준으로 테스트를 작성하게 된다.

2. 설치

1. RTL만 설치

  • npm: $ npm install --save-dev @testing-library/react
  • yarn: $ yarn add --dev @testing-library/react

2. Jest + RTL 등 함께 설치

  • npm: $ npm install --save-dev jest @testing-library/jest-dom @testing-library/react @types/jest
  • yarn: $ yarn add --dev jest @testing-library/jest-dom @testing-library/react @types/jest
  • 설치하는 항목:
    • jest: Jest 설치, 참고로 CRA로 생성된 프로젝트는 기본적으로 Jest가 내장되어 있음.
    • @testing-library/jest-dom: Jest의 기본 기능을 확장하여 DOM 검증을 더 편리하고 가독성있게 작성할 수 있도록 제공하는 에드온(기본적인 기능에 추가적인 기능이나 확장을 제공하는 도구)
    • @testing-library/react: RTL
    • @types/jest: 타입 설치

3. RTL의 기본 함수 및 구조

1) render() 함수

render() 함수는 React 컴포넌트를 렌더링하고, 테스트에 필요한 유틸리티 함수를 반환하는 함수이다. 테스트 코드에서 컴포넌트를 렌더링하고 이를 검증하는 데 사용된다.

render() 함수는 인자로 렌더링할 React 컴포넌트를 받는다. 그리고 RTL에서 제공하는 모든 쿼리 함수와 기타 유틸리티 함수를 담고 있는 객체를 리턴한다.

import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('컴포넌트가 올바르게 렌더링되었는지 확인', () => {
  // 컴포넌트를 렌더링하고 유틸리티 함수들을 반환합니다.
  const { getByText, getByRole, queryByText } = render(<MyComponent />);

  // 테스트 코드에서 반환된 유틸리티 함수들을 사용하여 컴포넌트를 검증합니다.
  const textElement = getByText('Hello, World!');
  const buttonElement = getByRole('button');
  expect(textElement).toBeInTheDocument();
  expect(buttonElement).toBeInTheDocument();

  // 존재하지 않는 요소를 검색하면 null을 반환합니다.
  const nonExistentElement = queryByText('Non-existent');
  expect(nonExistentElement).toBeNull();
});

2) Queries

RTL에서 Query는 특정 요소(Elements)를 DOM에서 검색하고 선택하는 데 사용되는 메서드들을 의미한다. 또한 테스트 코드에서 컴포넌트의 상태와 동작을 검증하는 데 활용된다.

a. RTL에서 요소를 검색하는 주요 접근 방식

  1. ByRole: 역할에 따라 요소를 찾습니다. 예를 들어 버튼, 링크, 헤딩 등의 역할을 기준으로 요소를 검색할 수 있다. 이는 웹 접근성을 고려하여 테스트를 작성할 때 유용하다.
  2. ByLabelText: 레이블 텍스트를 기준으로 요소를 찾는다. 보통 입력 필드와 연결된 레이블을 찾을 때 사용된다.
  3. ByText: 텍스트 내용을 기준으로 요소를 찾는다. 요소에 표시되는 텍스트를 기반으로 검색할 때 사용된다.
  4. ByPlaceholderText: 입력 필드의 placeholder 텍스트를 기준으로 요소를 찾는다.
  5. ByDisplayValue: 입력 요소(input, textarea, select 등)의 현재 표시된 값을 기준으로 요소를 찾는다. 이는 사용자가 입력한 값을 확인하거나 입력 필드의 초기값을 확인하는 데 사용된다.
  6. ByAltText: 이미지 요소의 대체 텍스트(alt 속성)를 기준으로 요소를 찾는다.
  7. ByTitle: 요소의 제목(title 속성)을 기준으로 요소를 찾는다. 일반적으로 툴팁이나 추가 정보를 제공하는 데 사용되며, 테스트 중에 툴팁이나 추가 정보가 제대로 표시되는지 확인하는 데 사용될 수 있.
  8. ByTestId: 요소에 특정한 테스트 ID를 할당하여 해당 ID를 기준으로 요소를 찾는다. 주로 컴포넌트의 특정 부분을 테스트하는 데 사용된다.

RTL의 Query 메서드는 크게 세 가지 유형(type)으로 나눌 수 있다. **get**, **find**, **query**이다.

b. Types of quries (get, find, query)

  • getBy: 주어진 쿼리에 해당하는 요소를 찾지 못할 경우 예외를 발생시키는 메서드이다. 따라서 요소를 찾지 못할 경우 테스트가 실패하게 된다. 따라서 **getBy** 메서드를 주로 사용할 때는 해당 요소가 반드시 존재해야 하는 경우에 사용된다.
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

render(<MyComponent />);

// 특정 텍스트를 포함하는 요소를 찾습니다.
const element = screen.getByText('Hello, World!');
expect(element).toBeInTheDocument();
  • findBy : 비동기 테스트를 위한 유틸리티 메서드이다. 이 메서드는 주어진 조건을 충족하는 요소가 나타날 때까지 대기하고, 해당 요소를 반환한다. findBy 메서드들은 비동기적으로 작동한다. 이들은 특정 요소가 나타날 때까지 기다린 후 해당 요소를 반환하거나, 기본 제한 시간이 초과되면 타임아웃 예외를 발생시킨다. 따라서 findBy 메서드들은 Promise를 반환하므로 async/await 키워드를 사용하여 비동기적으로 처리해야 한다.
import React from 'react';
import { render, screen } from '@testing-library/react';
import AsyncComponent from './AsyncComponent';

test('AsyncComponent renders data after loading', async () => {
  render(<AsyncComponent />);
  
  // 데이터가 로딩되기를 기다림
  const dataElement = await screen.findByText('Loaded Data');

  // 데이터가 로딩된 후의 요소가 존재하는지 확인
  expect(dataElement).toBeInTheDocument();
});
  • queryBy(): 주어진 쿼리에 해당하는 요소를 찾지 못할 경우 **null**을 반환하는 메서드입니다. Get과 마찬가지로 요소를 찾지 못할 경우 예외를 발생시키지 않습니다. 따라서 Query 메서드를 사용할 때는 요소의 존재 여부를 확인하고 그에 따라 테스트를 처리할 수 있습니다.
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

render(<MyComponent />);

// 특정 텍스트를 포함하는 요소를 찾습니다.
const element = screen.queryByText('Hello, World!');
if (element) {
  // 요소가 존재하는 경우
  expect(element).toBeInTheDocument();
} else {
  // 요소가 존재하지 않는 경우
  console.log('해당 요소를 찾을 수 없습니다.');
}
  • getBy* : 동기적으로 처리되며 타겟을 찾지 못할 시 에러를 발생시킨다.
  • findBy* : 비동기적으로 처리되며 타겟을 찾지 못할 시 에러를 발생시킨다.
  • queryBy* : 동기적으로 처리되며 타겟을 찾지 못할 시 null을 반환한다.

d. 요소 검색 시 단일(single) / 다중(multiple) 요소 구분하여 사용

  • Single Element: **getBy, queryBy, findBy**
  • Multiple Elements: **getAllBy, queryAllBy, findAllBy**

3) screen 함수

screen은 현재 렌더링이 진행되고 있는 화면을 의미한다. RTL에서 screen은 테스트 환경에서 화면을 대표하는 객체이다.

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

const Button = () => (
  <button>Click Me!</button>
);

test('<Button />', () => {
  render(<Button />);

  screen.getByText(/click me/i);
});
  • **screen.debug() :** 메서드는 현재 렌더링된 DOM tree의 상태를 콘솔에 출력하는 메서드이다. 주로 디버깅 목적으로 사용되며, 테스트 중에 컴포넌트의 상태나 구조를 살펴볼 때 유용하다. DOM tree의 상태가 변경되기 전에 이 메서드를 호출하고 변경된 후의 시점에 다시 호출하면 변경 전후를 비교할 수 있다.
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('Counter increments correctly', () => {
  render(<Counter />);
  
  // DOM tree 상태 출력 (초기 상태)
  screen.debug();

  // 버튼 클릭
  const incrementButton = screen.getByText('Increment');
  fireEvent.click(incrementButton);

  // DOM tree 상태 출력 (변화 후)
  screen.debug();
});

4) User Interactions

@testing-library/user-event는 RTL에서 사용자 동작을 모방하는 유틸리티 라이브러리로, CRA에 기본적으로 포함되어 있다. 이 라이브러리를 사용하면 실제 사용자가 웹 애플리케이션에서 수행하는 동작을 테스트 코드에서 모방할 수 있다. 주로 입력 필드에 텍스트를 입력하거나 클릭, 포커스 이동 등의 동작을 시뮬레이션할 때 사용된다.

그런데, RTL에서 제공하는 User Actions 카테고리에 보면 **fireEvent()**라는 메서드가 있다. 이 역시 유저 상호작용을 컴포넌트에서 테스트하기 위해 제공되는 함수이다. 다만, 공식 문서에서도 대부분의 프로젝트에는 **fireEvent**에 대한 사용 사례가 몇 가지 있지만, 대부분의 경우 **@testing-library/user-event**를 사용하라고 한다.

user-event와 fireEvent()의 차이점?

fireEvent는 DOM 이벤트를 발생시킨다.

user-event 라이브러리는 사용자의 실제 상호작용을 시뮬레이트한다. user-event는 사용자 상호작용을 더 잘 모방하므로 fireEvent보다 더 실제 브라우저와의 상호 작용을 유사하게 이벤트의 상호 작용을 테스트할 수 있다.

따라서 대부분의 경우 @testing-library/user-event를 사용하는 것이 더 좋은 선택이다. 사용자의 실제 동작을 모방하기 때문에 더 신뢰성 있고 테스트 커버리지가 더 높은 테스트를 작성할 수 있기 때문이다.

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';

test('텍스트 입력 후 버튼 클릭 시 입력 값이 초기화되는지 확인', () => {
  render(<App />);

  // 입력 필드를 찾고 'Hello, World!'를 입력합니다.
  const inputField = screen.getByPlaceholderText('Type something...');
  userEvent.type(inputField, 'Hello, World!');

  // 입력 필드에 올바른 값을 입력했는지 확인합니다.
  expect(inputField).toHaveValue('Hello, World!');

  // 버튼을 클릭하여 입력 값을 지웁니다.
  const button = screen.getByText('Clear');
  userEvent.click(button);

  // 입력 필드가 비어 있는지 확인합니다.
  expect(inputField).toHaveValue('');
});

Reference.

profile
😸

0개의 댓글