Front-end/Test

[TDD] ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŒ… 2ํŽธ: React Testing Library(RTL) ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ๐Ÿงช

xeunnie 2025. 2. 3. 01:00
728x90
๋ฐ˜์‘ํ˜•

React Testing Library(RTL) ํ†บ์•„๋ณด๊ธฐ! ๐Ÿงช

 

“์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹ ๊ทธ๋Œ€๋กœ ํ…Œ์ŠคํŠธํ•˜๋ผ!”

 

React Testing Library(RTL)์€ ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

๋‹จ์ˆœํžˆ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ๊ณ ๋ คํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค.


1๏ธโƒฃ React Testing Library(RTL)์ด๋ž€?

๐Ÿ’ก RTL์€ ๋‹จ์ˆœํžˆ UI์˜ DOM ์š”์†Œ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ,

“์‚ฌ์šฉ์ž๊ฐ€ ์›น์„ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ๋•Œ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ”์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ’ก๊ธฐ์กด์˜ Enzyme๊ณผ ๋น„๊ตํ•˜๋ฉด?

  • โœ… Enzyme: ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ์ค‘์ ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ
  • โœ… RTL: ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ…Œ์ŠคํŠธ

โœ” Jest vs RTL ๋น„๊ต

ํ•ญ๋ชฉ Jest RTL
๋ชฉ์  ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ (์‹คํ–‰ ํ™˜๊ฒฝ) React ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ
๋ Œ๋”๋ง ์—†์Œ render() API ์ œ๊ณต
๋น„๋™๊ธฐ ์ง€์› async/await ์‚ฌ์šฉ findBy, waitFor ์ง€์›
์œ ์ € ์ด๋ฒคํŠธ fireEvent ์‚ฌ์šฉ userEvent ์ง€์› (๋” ์ง๊ด€์ )
์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ ์ง€์› Jest์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

โœ” ์ฆ‰, Jest๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ™˜๊ฒฝ์ด๊ณ , RTL์€ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ

โœ… Jest๋Š” ์‹คํ–‰ ๋„๊ตฌ (ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰)

โœ… RTL์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹ค์ œ ์‚ฌ์šฉ์ž์˜ ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋„๊ตฌ (UI ๊ฒ€์ฆ)


2๏ธโƒฃ RTL์˜ ์ฒ ํ•™: “์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ค‘์‹ฌ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ผ!”

โœ… “React Testing Library๋Š” ๊ตฌํ˜„ ์„ธ๋ถ€ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ผ, ์‚ฌ์šฉ์ž์˜ ์ž…์žฅ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.”

๐Ÿ’ก ๊ธฐ์กด ํ…Œ์ŠคํŠธ ๋ฐฉ์‹ (๊ตฌํ˜„ ์„ธ๋ถ€ ์‚ฌํ•ญ ํ…Œ์ŠคํŠธ)

const { getByTestId } = render(<Counter />);
expect(getByTestId("counter-value").textContent).toBe("0");

 

โŒ data-testid๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ…Œ์ŠคํŠธ → ์‹ค์ œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊ณผ ๋™๋–จ์–ด์ง„ ๋ฐฉ์‹

๐Ÿ’ก ๊ถŒ์žฅ๋˜๋Š” RTL ํ…Œ์ŠคํŠธ ๋ฐฉ์‹

const { getByRole } = render(<Counter />);
expect(getByRole("heading", { level: 1 })).toHaveTextContent("0");

 

โœ” ์ ‘๊ทผ์„ฑ ์†์„ฑ(A11y)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์š”์†Œ๋ฅผ ์ฐพ๊ธฐ ๋•Œ๋ฌธ์—, ์‹ค์ œ ์‚ฌ์šฉ์ž์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

 

โœ… ์ฆ‰, HTML ์š”์†Œ์˜ ๊ตฌํ˜„ ์„ธ๋ถ€ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ผ,

์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ๋ฐฉ์‹์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค!

 

๐Ÿ“Œ RTL์ด ํ•„์š”ํ•œ ์ด์œ ?

  • โœ… ์‚ฌ์šฉ์ž ์ค‘์‹ฌ ํ…Œ์ŠคํŠธ → ๋ฒ„ํŠผ ํด๋ฆญ, ์ž…๋ ฅ ๋“ฑ ์‹ค์ œ ์‚ฌ์šฉ์ž์™€ ์œ ์‚ฌํ•œ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • โœ… ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„๋ณด๋‹ค ์‹ค์ œ ๋™์ž‘ ๊ฒ€์ฆ
  • โœ… Jest์™€ ์™„๋ฒฝํ•œ ํ˜ธํ™˜
  • โœ… ๋…๋ฆฝ์ ์ธ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์ œ๊ณต (DOM ์กฐ์ž‘ ์—†์ด ๊ฐ€์ƒ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰)

3๏ธโƒฃ React Testing Library ์„ค์น˜ ๋ฐ ์„ค์ •

๐Ÿ”ง ์„ค์น˜ ๋ฐฉ๋ฒ•

๋จผ์ €, ํ”„๋กœ์ ํŠธ์— React Testing Library๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

npm install --save-dev @testing-library/react @testing-library/jest-dom
  • @testing-library/react: React ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›
  • @testing-library/jest-dom: Jest์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด DOM ํ…Œ์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ํ•จ

๐Ÿ›  Jest ์„ค์ • (package.json)

"scripts": {
  "test": "react-scripts test"
}

 

Jest๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ react-scripts์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ๋ณ„๋„์˜ ์„ค์น˜ ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


4๏ธโƒฃ React Testing Library ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

๐Ÿ” ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

๊ธฐ๋ณธ์ ์ธ React ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ ์˜ˆ์ œ๋ฅผ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์˜ˆ์ œ: ๋ฒ„ํŠผ ํด๋ฆญ ํ…Œ์ŠคํŠธ

import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
import Counter from "../Counter"; // ํ…Œ์ŠคํŠธํ•  ์ปดํฌ๋„ŒํŠธ

test("๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์นด์šดํŠธ๊ฐ€ ์ฆ๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค", () => {
  render(<Counter />);

  const button = screen.getByRole("button", { name: "์ฆ๊ฐ€" });
  expect(button).toBeInTheDocument();

  fireEvent.click(button);
  expect(screen.getByText("์นด์šดํŠธ: 1")).toBeInTheDocument();
});

 

๐Ÿ›  ์ฃผ์š” ํ•จ์ˆ˜ ๋ฐ API ์„ค๋ช…

ํ•จ์ˆ˜ ์„ค๋ช…
render(<Component />)  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€์ƒ DOM์— ๋ Œ๋”๋ง
screen.getByText(text) ํŠน์ • ํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ง„ ์š”์†Œ ์ฐพ๊ธฐ
screen.getByRole(role, options) ์—ญํ•  ๊ธฐ๋ฐ˜ ์š”์†Œ ์ฐพ๊ธฐ (button, heading ๋“ฑ)
fireEvent.click(element) ํด๋ฆญ ์ด๋ฒคํŠธ ํŠธ๋ฆฌ๊ฑฐ
expect(element).toBeInTheDocument() ์š”์†Œ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ

5๏ธโƒฃ RTL ํ•ต์‹ฌ API & ์ฃผ์š” ๋ฉ”์„œ๋“œ

๐Ÿ“Œ render() - ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง

import { render } from "@testing-library/react";
import Counter from "./Counter";

test("Counter๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š”์ง€ ํ™•์ธ", () => {
  render(<Counter />);
});

 

โœ” render()๋ฅผ ํ†ตํ•ด React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹ค์ œ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์ฒ˜๋Ÿผ ๋ Œ๋”๋ง

๐Ÿ“Œ screen - DOM ์š”์†Œ ์ฐพ๊ธฐ

import { render, screen } from "@testing-library/react";
import Counter from "./Counter";

test("๋ฒ„ํŠผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ", () => {
  render(<Counter />);
  expect(screen.getByText("์ฆ๊ฐ€")).toBeInTheDocument();
});

 

โœ” screen.getByText("์ฆ๊ฐ€") → ์‹ค์ œ ์‚ฌ์šฉ์ž์ฒ˜๋Ÿผ ํ…์ŠคํŠธ๋กœ ์š”์†Œ๋ฅผ ์ฐพ๋Š” ๋ฐฉ์‹


6๏ธโƒฃ ์ด๋ฒคํŠธ ํ…Œ์ŠคํŠธ (์œ ์ € ์ƒํ˜ธ์ž‘์šฉ)

๐Ÿ“Œ userEvent vs fireEvent ์ฐจ์ด์ 

๋ฉ”์„œ๋“œ ์„ค๋ช…  ์˜ˆ์ œ
fireEvent ๊ธฐ๋ณธ DOM ์ด๋ฒคํŠธ ํŠธ๋ฆฌ๊ฑฐ fireEvent.click(button)
userEvent ์‹ค์ œ ์‚ฌ์šฉ์ž ์ด๋ฒคํŠธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ userEvent.click(button)

๐Ÿ’ก ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ํ…Œ์ŠคํŠธ ์˜ˆ์ œ

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "./Counter";

test("๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์นด์šดํŠธ ์ฆ๊ฐ€", async () => {
  render(<Counter />);
  const button = screen.getByRole("button", { name: "์ฆ๊ฐ€" });

  await userEvent.click(button);
  expect(screen.getByText("1")).toBeInTheDocument();
});

 

โœ” userEvent.click()์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘


7๏ธโƒฃ ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ (findBy, waitFor)

โœ” ๋น„๋™๊ธฐ UI๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค๋ฉด findBy ๋˜๋Š” waitFor ์‚ฌ์šฉ

๐Ÿ’ก API ํ˜ธ์ถœ ํ›„ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š”์ง€ ํ™•์ธ

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "./Counter";

test("๋ฒ„ํŠผ ํด๋ฆญ ํ›„ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ™•์ธ", async () => {
  render(<Counter />);
  const button = screen.getByText("์ฆ๊ฐ€");

  userEvent.click(button);
  expect(await screen.findByText("๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ")).toBeInTheDocument();
});

 

โœ” findByText๋Š” ๋น„๋™๊ธฐ ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ

๐Ÿ’ก API ์‘๋‹ต์ด ๋๋‚œ ํ›„ UI๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š”์ง€ ํ™•์ธ

await waitFor(() => expect(screen.getByText("๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ")).toBeInTheDocument());

 

โœ” waitFor()์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ๋ฒˆ UI ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ์•ˆ์ •์ ์ธ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ


8๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๊ธฐ๋ฒ•

1. ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์˜ˆ์ œ: Input ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ

import { render, screen } from "@testing-library/react";
import Input from "../Input";

test("์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•œ๋‹ค", () => {
  render(<Input />);
  expect(screen.getByPlaceholderText("์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”")).toBeInTheDocument();
});

 

2. ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ํ…Œ์ŠคํŠธ

์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ์ž…๋ ฅํ•˜๋Š” ๋™์ž‘์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

import { render, screen, fireEvent } from "@testing-library/react";
import Input from "../Input";

test("์ž…๋ ฅ ํ•„๋“œ์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•œ๋‹ค", () => {
  render(<Input />);
  const input = screen.getByPlaceholderText("์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”");

  fireEvent.change(input, { target: { value: "Alice" } });
  expect(input.value).toBe("Alice");
});

 

3. ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ

API ํ˜ธ์ถœ ํ›„ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

๐Ÿ“Œ ์˜ˆ์ œ: ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

import { render, screen, waitFor } from "@testing-library/react";
import UserList from "../UserList";

test("์œ ์ € ๋ชฉ๋ก์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•œ๋‹ค", async () => {
  render(<UserList />);

  await waitFor(() => {
    expect(screen.getByText("์œ ์ € ๋ชฉ๋ก")).toBeInTheDocument();
  });
});

9๏ธโƒฃ ๋””๋ฒ„๊น… ๋ฐ ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ๋Œ€์ฒ˜๋ฒ•

โœ” ํ…Œ์ŠคํŠธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, screen.debug()๋ฅผ ํ™œ์šฉํ•˜๋ฉด DOM ๊ตฌ์กฐ๋ฅผ ํ™•์ธ ๊ฐ€๋Šฅ

๐Ÿ’ก ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ๋””๋ฒ„๊น… ์˜ˆ์ œ

test("๋ฒ„ํŠผ ํด๋ฆญ ํ›„ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ", async () => {
  render(<Counter />);
  screen.debug(); // ํ˜„์žฌ DOM ์ƒํƒœ ์ถœ๋ ฅ
});

 

โœ” debug()๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ˜„์žฌ ๋ Œ๋”๋ง๋œ DOM์„ ํ„ฐ๋ฏธ๋„์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ


7๏ธโƒฃ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ (Snapshot Testing)

โœ” ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋Š” ๋ Œ๋”๋œ UI๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ’ก ๊ธฐ๋ณธ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ

import { render } from "@testing-library/react";
import Counter from "./Counter";

test("์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ", () => {
  const { asFragment } = render(<Counter />);
  expect(asFragment()).toMatchSnapshot();
});

 

โœ” toMatchSnapshot()์„ ์‚ฌ์šฉํ•˜๋ฉด UI ๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ฐ€๋Šฅ


๐Ÿ”Ÿ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

1. ์ปค์Šคํ…€ ๋ Œ๋”๋Ÿฌ ์‚ฌ์šฉ๋ฒ•

ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๊ณตํ†ต์ ์œผ๋กœ ํ•„์š”ํ•œ Provider๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { render } from "@testing-library/react";
import { ThemeProvider } from "styled-components";
import theme from "../theme";

const customRender = (ui, options) =>
  render(ui, { wrapper: ({ children }) => <ThemeProvider theme={theme}>{children}</ThemeProvider>, ...options });

export * from "@testing-library/react";
export { customRender as render };

 

์ด์ œ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์—์„œ render() ๋Œ€์‹  customRender()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

2. Mocking ๋ฐ Spying

API ์š”์ฒญ์„ Mock ์ฒ˜๋ฆฌํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์˜ˆ์ œ: API ์š”์ฒญ์„ Mock ์ฒ˜๋ฆฌ

import { render, screen } from "@testing-library/react";
import axios from "axios";
import UserList from "../UserList";

jest.mock("axios");

test("API์—์„œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค", async () => {
  axios.get.mockResolvedValue({ data: [{ id: 1, name: "Alice" }] });

  render(<UserList />);
  expect(await screen.findByText("Alice")).toBeInTheDocument();
});

๐Ÿ”Ÿ1๏ธโƒฃ ๋ชจ๋ฒ” ์‚ฌ๋ก€

โœ… 1๏ธโƒฃ ๋„ˆ๋ฌด ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์„ ํ…Œ์ŠคํŠธํ•˜์ง€ ๋ง ๊ฒƒ

โŒ ์ž˜๋ชป๋œ ์˜ˆ:

expect(component.state.count).toBe(1); // ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€

 

โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ:

fireEvent.click(button);
expect(screen.getByText("์นด์šดํŠธ: 1")).toBeInTheDocument();

 

โœ… 2๏ธโƒฃ ID๋ณด๋‹ค ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์…€๋ ‰ํ„ฐ ์‚ฌ์šฉ

โŒ ์ž˜๋ชป๋œ ์˜ˆ:

screen.getByTestId("submit-button");

 

โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ:

screen.getByRole("button", { name: "์ œ์ถœ" });

 

 

โœ… 3๏ธโƒฃ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜ ์ผ๊ด€์„ฑ ์œ ์ง€

describe("๋กœ๊ทธ์ธ ๋ฒ„ํŠผ", () => {
  test("๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋กœ๊ทธ์ธ ์š”์ฒญ์ด ์ „์†ก๋œ๋‹ค", () => {
    // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ...
  });
});

๐ŸŒŸ ์ •๋ฆฌ: React Testing Library ํ•ต์‹ฌ ํฌ์ธํŠธ

โœ… “์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋ผ!”

โœ… render() & screen์„ ํ™œ์šฉํ•ด ์š”์†Œ ์ฐพ๊ธฐ

โœ… userEvent๋ฅผ ์‚ฌ์šฉํ•ด ์‹ค์ œ ์‚ฌ์šฉ์ž์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ

โœ… ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ (findBy, waitFor) ํ•„์ˆ˜ ํ™œ์šฉ

โœ… ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ screen.debug()๋กœ ๋””๋ฒ„๊น…

โœ… ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋กœ UI ๋ณ€๊ฒฝ ๊ฐ์ง€

 

๐ŸŒท ์ „์„ค์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ด…์‹œ๋‹น! ๐ŸŒท ๐ŸŽ‰

728x90
๋ฐ˜์‘ํ˜•