-
[React Testing Library] 기본 활용법, 유저 이벤트 테스트React/jest 2023. 8. 4. 20:41
0. Introduction
Jest를 사용하면서 대부분 함수 단위의 유닛 테스트나 통합 테스트만 사용했었다. 팀 단위 프로젝트의 CI/CD를 구상하면서 리액트 컴포넌트가 잘 렌더링되고 있는지 확인이 필요했고, CRA에서 기본적으로 지원하는 React Testing Library를 사용해보기로 하였다.
1. 기본 개념
본 라이브러리를 학습하기 위해 코딩 앙마님의 React Testing Library 강의 영상을 수강하였고, 기본 개념과 코드들은 해당 영상을 참고하여 작성하였다.
1.1 컴포넌트 렌더링 테스트
가장 기본적인 App 컴포넌트가 렌더링되고 있는지에 대한 테스트를 작성해보자. 아래의 테스트는 App 컴포넌트를 렌더링하여 heading역할을 하는 요소를 받아온다. 그리고 해당 엘리먼트가 화면에 존재하는지 검증하면 테스트가 종료된다.
- render() 특정 컴포넌트를 렌더링
- screen 객체의 쿼리 메소드로 HTML element에 접근
// App.test.js import { render, screen } from '@testing-library/react'; test('<App /> 렌더링시 / 경로로 렌더링 되나요?', async () => { render(<App />); const headingEl = screen.getByRole('heading'); expect(headingEl).toBeInTheDocument(); });
1.2 jest-dom의 custom matchers
toBeInTheDocument와 같이 matcher를 통해 특정 DOM 요소의 상태를 테스트할 수 있으며, 공식 문서에서는 다양한 matcher를 제공하고 있다. 회원이 아닌 경우 버튼을 비활성화하고 안내 문구를 빨간색으로 나타내는 JoinBtn 컴포넌트를 테스트해보자.
test("회원이 아닌 경우 버튼을 비활성화합니다. 안내 문구는 빨간색입니다.", () =>{ render(<JoinBtn isMember={false}/>); const btnEl = screen.getByRole("button"); const textEl = screen.getByRole("heading"); expect(btnEl).toBeInTheDocument(); // 해당 버튼이 화면에 존재하나요? expect(textEl).toBeInTheDocument(); // 해당 문구가 화면에 존재하나요? expect(btnEl).toBeDisbaled(); // 버튼 비활성화 되어있나요? expect(btnEl).toHaveStyle({ // 문구가 빨간색인가요? color: "red", }); })
테스트에 용이한 컴포넌트를 위해서 가변적인 데이터를 props로 외부에서 주입하자!
1.3 HTML element를 찾는 쿼리
렌더링된 컴포넌트에서 테스트를 위한 요소를 찾기 위해 공식 문서에서는 다양한 쿼리들을 제공한다. 이 쿼리들을 단일/여러개의 요소를 찾을 것인지, 요소를 어떤 방식으로 찾을 것인지에 따라 분류할 수 있다.
[표 1. Summary Table: Type of Query(출처: 공식 문서)]1.3.1 단일 / 여러 개의 요소 찾기
하나의 요소를 찾는 쿼리들은 1 개의 요소가 매칭되었을 때 해당 요소를 반환하고, 여러 개가 매칭되면 [표 1]과 같이 에러를 발생시킨다. 요소를 찾는 방법은 역할, 텍스트, 지정한 테스트 id 등으로 다양하며 요소를 특정하기 위해 옵션을 추가하기도 한다. Wrapper로만 사용하는 div는 테스트 id를 붙여 찾을 수 있으나 테스트만을 위한 코드가 프로젝트에 추가되므로 최후의 방법으로 사용하는 것이 좋다.
<div data-testid="my-div"> <h1>마이페이지</h1> <label htmlFor="username">이름</label> <input type="text" id="username" /> </div>
const textEl = screen.getByRole("heading",{ level: 1, // h1 tag 찾기 }); const inputEl = screen.getByRole("textbox", { name: "이름", //label's children text 이름으로 찾기 - htmlFor id가 연결되어 있어야 함 }); const inputEl = screen.getByLabelText("이름"); // label이 아닌 연결된 textbox를 찾음 const inputEl = screen.getByLabelText("이름", { selector: input, }); const inputElements = screen.getAllByRole("textbox"); // 배열을 요소로 반환 const divEl = screen.getByTextId("my-div");
1.3.2 여러 방식으로 요소 찾기
먼저 요소를 찾는 방식에 따라 아래와 같이 3 가지 타입으로 먼저 분류해보자.
- get... 일치하는 요소가 없으면 에러 발생
- query... 일치하는 요소가 없으면 null, 빈 배열 반환 ➡️ 없는 요소 테스트에 활용!
- find... 프로미스를 반환(default = 1 초)
const liElements = screen.getAllByRole("listitem"); // 요소 없으면 아예 에러뜸 const liElements = screen.queryByRole("listitem"); // null 반환 const liElements = screen.queryAllByRole("listitem"); // 빈 배열 반환 test("잠시 후 제목이 나타납니다." async() =>{ render(<UserList users={users}/>); screen.debug(); // 렌더링된 DOM 트리 확인할 수 있는 디버깅 모드 const titleEl = await screen.findByRole("heading", { name: "사용자 목록" }, { timeout: 2000, // default = 1 초 }); screen.debug(); });
2. 유저 이벤트 테스트 user-event-test
코딩 앙마님의 React Testing Library user-event-test 강의 바로 가기
2.0 user-event setting
package.json의"@testing-library/user-event"의 버전을 13 -> 14로 업그레이드 해준다.
설명 : https://testing-library.com/docs/ecosystem-user-event
{"name": "test-framework","version": "0.1.0","private": true,"dependencies": {"@testing-library/jest-dom": "^5.17.0","@testing-library/react": "^13.4.0","@testing-library/user-event": "^14.4.3", // 이부분만 수정할 것!"react": "^18.2.0","react-dom": "^18.2.0","react-scripts": "5.0.1","web-vitals": "^2.1.4"},그 후 npm install하여@testing-library/user-event업데이트
2.1 click 이벤트
click(element, eventInit, options)
dblClick(element, eventInit, options)
먼저, /component/Login.js를 생성해준다.
import { useState } from 'react';
export default function Login() {const [isLogin, setIsLogin] = useState(false);
const onClickHandler = () => {setIsLogin(!isLogin);};
return (<button onClick={onClickHandler}>{isLogin ? 'Logout' : 'Login'} </button>);}Login.test.js 파일은 다음처럼 작성해준다.
import Login from './Login';import { render, screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';
describe('Login test', () => {test('처음에는 Login 버튼이 있다.', () => {render(<Login />);const buttonElement = screen.getByRole('button');expect(buttonElement).toHaveTextContent('Login');});
const user = userEvent.setup();test('한 번 클릭하면 Logout으로 변한다.', async () => {render(<Login />);const buttonElement = screen.getByRole('button');
await user.click(buttonElement);expect(buttonElement).toHaveTextContent('Logout');});const user = userEvent.setup(); 후 await + user.click(buttonElement)를 사용하면 된다.
2.2 keyup이벤트
type(element, text, [options])
keyboard(text, options)
이외에도 userEvent.type을 사용해서 테스트 하는 방법이 있다.
사용 방법 :
userEvent.type(screen.getByRole('textbox'), 'Hello,{enter}World!')
userEvent.keyboard('foo')
Special characters
2.3 upload 이벤트
upload(element, file, [{ clickInit, changeInit }], [options])
test('upload file', () => {const file = new File(['hello'], 'hello.png', { type: 'image/png' });
render(<div><label htmlFor="file-uploader">Upload file:</label><input id="file-uploader" type="file" /></div>);const input = screen.getByLabelText(/upload file/i);userEvent.upload(input, file);
expect(input.files[0]).toStrictEqual(file);expect(input.files.item(0)).toStrictEqual(file);expect(input.files).toHaveLength(1);});2.4 specialChars
user-event에서 specialChars를 따로 부를 수 있다.
import React, { useState } from 'react';import { render, screen } from '@testing-library/react';import userEvent, { specialChars } from '@testing-library/user-event';
const InputElement = () => {const [currentValue, setCurrentValue] = useState('This is a bad example');return (<div><label htmlFor="my-input">Example:</label><inputid="my-input"type="text"value={currentValue}onChange={(e) => setCurrentValue(e.target.value)}/></div>);};
test('delete characters within the selectedRange', () => {render(<InputElement />);const input = screen.getByLabelText(/example/i);input.setSelectionRange(10, 13);userEvent.type(input, `${specialChars.backspace}good`);
expect(input).toHaveValue('This is a good example');});2.5 기타
clear(element)
selectOptions(element, values, options)
deselectOptions(element, values, options)
tab({shift, focusTrap})
hover(element, options)
unhover(element, options)
paste(element, text, eventInit, options)
'React > jest' 카테고리의 다른 글
[React Testing Library] msw를 활용한 mock API 테스트 (0) 2023.08.04 [Jest] 기본 활용법 (0) 2023.08.03