1. RTL만 설치
$ npm install --save-dev @testing-library/react
$ yarn add --dev @testing-library/react
2. Jest + RTL 등 함께 설치
$ npm install --save-dev jest @testing-library/jest-dom @testing-library/react @types/jest
$ 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
: 타입 설치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();
});
RTL에서 Query는 특정 요소(Elements)를 DOM에서 검색하고 선택하는 데 사용되는 메서드들을 의미한다. 또한 테스트 코드에서 컴포넌트의 상태와 동작을 검증하는 데 활용된다.
a. RTL에서 요소를 검색하는 주요 접근 방식
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) 요소 구분하여 사용
**getBy, queryBy, findBy
****getAllBy, queryAllBy, findAllBy**
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();
});
@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.