간단한 예제를 통해, React Testing Library를 실습해보고자 한다.
먼저 실습에 앞서, TDD 개념에 대해 짚고 가보자.
TDD란
Test Driven Development의 약자로, 한국어로는 테스트 주도 개발이라고 명칭한다.
테스트 주도 개발이란 실제 코드를 작성하기 전에 테스트 코드를 먼저 작성하는 것을 의미한다.
즉, 기능 개발에 앞서 테스트 코드를 작성하고 이 테스트 코드를 바탕으로 기능 구현 후, 이를 검증하는 방식이다.
TDD는 크게 3가지 단계로 나눌 수 있다.
- Red : 코드를 작성하기 전에 테스트 코드 작성 단계.
코드를 작성하기 전이므로 당연히 테스트는 실패할 것이다. - Green : 테스트를 통과 시키기 위해 코드를 작성하는 단계.
- Refactor : 코드를 리팩토링 하는 단계.
TDD의 장점
- 테스트 코드 자체가 문서가 될 수 있다.
- 기획 및 사용자의 요구사항을 충족하는지를 가시적으로 확인할 수 있으며, 추가적인 인사이트를 얻을 수 있다.
- 유지보수 관점에서, 다른 작업자가 코드의 변경 사항을 반영할 때, 안전하게 업데이트 할 수 있다.
앞으로 진행될 실습에서는 TDD의 3가지 단계를 준수하면서 코드를 작성해본다.
1단계 : 테스트 코드 작성 해보기
테스트 시나리오
1. 카운터가 있다. 카운터의 초기값은 0이다.
2. 플러스 버튼이 있다.
3. 마이너스 버튼이 있다.
4. 플러스 버튼을 누르면 카운터가 1 증가한다.
5. 마이너스 버튼을 누르면 카운터가 1 감소한다.
6. 카운터가 0일 때 마이너스 버튼을 클릭하면 카운터는 0이다.
7. on/off 버튼을 누르면 플러스, 마이너스 버튼을 클릭할 수 없다.
테스트 코드
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import Counter from "../counter/Counter";
// 카운터는 0부터 시작한다.
test("카운터는 0부터 시작한다.", () => {
render(<Counter />);
const counterElement = screen.getByTestId("counter");
expect(counterElement).toHaveTextContent("0");
});
// + 버튼이 있다.
test("+ 버튼이 있다.", () => {
render(<Counter />);
const plusButton = screen.getByRole("button", { name: /plus/i });
expect(plusButton).toHaveTextContent("+");
});
// - 버튼이 있다.
test("- 버튼이 있다.", () => {
render(<Counter />);
const minusButton = screen.getByRole("button", { name: /minus/i });
expect(minusButton).toHaveTextContent("-");
});
// + 버튼을 클릭하면 카운터가 1 증가한다.
test("+ 버튼을 클릭하면 카운터가 1 증가한다.", () => {
render(<Counter />);
const plusButton = screen.getByRole("button", { name: /plus/i });
fireEvent.click(plusButton);
const counterElement = screen.getByTestId("counter");
expect(counterElement).toHaveTextContent("1");
});
// - 버튼을 클릭하면 카운터가 1 감소한다.
test("- 버튼을 클릭하면 카운터가 1 감소한다.", () => {
render(<Counter />);
const minusButton = screen.getByRole("button", { name: /minus/i });
fireEvent.click(minusButton);
const counterElement = screen.getByTestId("counter");
expect(counterElement).toHaveTextContent("0");
});
// 카운터가 0일 때 - 버튼을 클릭하면 카운터는 0이다.
test("카운터가 0일 때 - 버튼을 클릭하면 카운터는 0이다.", () => {
render(<Counter />);
const counterElement = screen.getByTestId("counter");
expect(counterElement).toHaveTextContent("0");
const minusButton = screen.getByRole("button", { name: /minus/i });
fireEvent.click(minusButton);
expect(counterElement).toHaveTextContent("0");
});
// on/off 버튼을 클릭하면 +, - 버튼이 비활성화된다.
test("on/off 버튼을 클릭하면 +, - 버튼이 비활성화된다.", () => {
render(<Counter />);
const onOffButton = screen.getByRole("button", { name: /onoff/i });
fireEvent.click(onOffButton);
const minusButton = screen.getByRole("button", { name: /minus/i });
const plusButton = screen.getByRole("button", { name: /plus/i });
expect(minusButton).toBeDisabled();
expect(plusButton).toBeDisabled();
});
FireEvent
- 사용자에 의해 발생되는 이벤트들을 구현하기 위해 사용한다.
- 스펙
fireEvent(node: HTMLElement, event: Event)
예시:
// <button>Submit</button>
fireEvent(
getByText(container, 'Submit'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
)
FireEvent[eventName]
- DOM 이벤트를 감지하기 더 편리하게 감지 하기 위해 사용한다.
- 스펙
fireEvent[eventName](node: HTMLElement, eventProperties: Object)
UserEvent
- 대부분의 경우 userEvent를 사용하라고 공식 문서에 권고되어 있다.
- fireEvent는 DOM 이벤트를 전송하는 반면, user-event는 전체 상호작용을 시뮬레이션하여 여러 이벤트를 전송하고 그 과정에서 추가 검사를 수행할 수 있다.
- 예를 들어 사용자가 텍스트 상자에 입력하면, 요소에 초점을 맞춘 다음 키보드 및 입력 이벤트가 발생하고 사용자가 입력할 때 요소의 선택 항목과 값이 조작되어야 한다.
- 이처럼, 어떤 하나의 이벤트가 발생되면, 이를 트리거하는 다른 이벤트들이 연쇄적으로 발생하는 것이 일반적이다.
- user-event를 사용하면 구체적인 이벤트 대신 사용자 상호 작용을 표현할 수 있다. 컴포넌트 간 상호작용을 테스트 할 수 있다.
2단계 : 테스트 코드를 통과 시킬 수 있는 코드 작성하기
테스트 코드에 맞는 리액트 컴포넌트를 작성한다.
//Counter.tsx 전문
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const [disabled, setDisabled] = useState(false);
const handleCountUp = () => {
setCount((prev) => prev + 1);
};
const handleCountDown = () => {
if (count === 0) return;
setCount((prev) => prev - 1);
};
const handleToggleOnOff = () => {
setDisabled((prev) => !prev);
};
return (
<div className="App-container">
<span data-testid="counter">{count}</span>
<div className="App-button-wrapper">
<button
aria-label="minus button"
onClick={handleCountDown}
disabled={disabled}
>
-
</button>
<button
aria-label="plus button"
onClick={handleCountUp}
disabled={disabled}
>
+
</button>
</div>
<button
aria-label="onoff button"
className="App-button"
onClick={handleToggleOnOff}
>
on/off
</button>
</div>
);
}
Counter.propTypes = {};
export default Counter;
* 참고
따라하며 배우는 리액트 테스트
https://medium.com/@admm/test-driven-development-in-react-is-easy-178c9c520f2f
'Frontend > React' 카테고리의 다른 글
프론트엔드 테스트 - (5) toBe() vs toEqual() vs toBeTruthy() (0) | 2024.03.26 |
---|---|
프론트엔드 테스트 - (4) RTL - 쿼리 우선 순위, userEvent (0) | 2024.03.25 |
프론트엔드 테스트 - (2) React Testing Library (0) | 2024.03.22 |
프론트엔드 테스트 - (1) 프롤로그, Jest 사용 방법 (0) | 2024.03.20 |
React Study - React.memo() (0) | 2023.07.28 |