Front-end/Test

[TDD] ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŒ… 3ํŽธ: React Testing Library(RTL) Mocking Spying ๐Ÿงช

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

๐ŸŽญ Mocking ๋ฐ Spying: ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ€์งœ ๋ฐ์ดํ„ฐ์™€ ๋™์ž‘์„ ์กฐ์ž‘ํ•˜๋Š” ๋ฒ•

ํ…Œ์ŠคํŠธ์—์„œ Mocking๊ณผ Spying์€ ํ•„์ˆ˜์ ์ธ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, ์™ธ๋ถ€ API ์š”์ฒญ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธ์ถœ, ์˜์กด์„ฑ ์žˆ๋Š” ํ•จ์ˆ˜ ๋“ฑ์˜ ์‹ค์ œ ๋™์ž‘์„ ๋Œ€์ฒดํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.


๐Ÿ“Œ 1. Mocking์ด๋ž€?

Mocking์€ ํŠน์ • ํ•จ์ˆ˜๋‚˜ ๋ชจ๋“ˆ์˜ ๋™์ž‘์„ ๊ฐ€์งœ(mock)๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

  • โœ”๏ธ ์™ธ๋ถ€ API ํ˜ธ์ถœ ์—†์ด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • โœ”๏ธ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์—†์–ด๋„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • โœ”๏ธ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ค„์ด๊ณ , ํ…Œ์ŠคํŠธ์˜ ์‹ ๋ขฐ์„ฑ ํ–ฅ์ƒ

Mocking ์˜ˆ์ œ: Jest๋ฅผ ํ™œ์šฉํ•œ API ์š”์ฒญ Mocking

import { render, screen } from "@testing-library/react";
import axios from "axios";
import UserList from "../UserList";  // ๊ฐ€์ •: ์œ ์ € ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ปดํฌ๋„ŒํŠธ
import { act } from "react-dom/test-utils";

jest.mock("axios"); // axios ๋ชจ๋“ˆ์„ Mock ์ฒ˜๋ฆฌ

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

  await act(async () => {
    render(<UserList />);
  });

  expect(await screen.findByText("Alice")).toBeInTheDocument();
});
  • โœ… jest.mock("axios"): axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ Mock ์ฒ˜๋ฆฌํ•˜์—ฌ ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š์Œ
  • โœ… mockResolvedValue(): axios.get()์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •
  • โœ… await act(): ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ ํ›„ UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋„๋ก ์ฒ˜๋ฆฌ
  • โœ… screen.findByText(): ๋น„๋™๊ธฐ ์š”์†Œ๋ฅผ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๊ฒ€์ฆ

๐Ÿ“Œ 2. Spying์ด๋ž€?

Spying์€ ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

  • โœ”๏ธ ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ๋ช‡ ๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
  • โœ”๏ธ ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ์ธ์ž๋ฅผ ๋ฐ›์•˜๋Š”์ง€ ๊ฒ€์ฆ
  • โœ”๏ธ ํŠน์ • ๋กœ์ง์ด ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ํ™•์ธ

Spying ์˜ˆ์ œ: Jest.spyOn()์„ ํ™œ์šฉํ•œ ํ•จ์ˆ˜ ๊ฐ์‹œ

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

test("๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด handleSubmit์ด ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค", () => {
  const handleSubmit = jest.fn(); // ๊ฐ€์งœ ํ•จ์ˆ˜(Mock Function) ์ƒ์„ฑ

  render(<LoginForm onSubmit={handleSubmit} />);

  const button = screen.getByRole("button", { name: "๋กœ๊ทธ์ธ" });
  fireEvent.click(button);

  expect(handleSubmit).toHaveBeenCalledTimes(1); // ํ•จ์ˆ˜๊ฐ€ 1๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
});
  • โœ… jest.fn(): ๊ฐ€์งœ ํ•จ์ˆ˜ ์ƒ์„ฑ (Mock Function)
  • โœ… fireEvent.click(): ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ด๋ฒคํŠธ ๋ฐœ์ƒ
  • โœ… expect(handleSubmit).toHaveBeenCalledTimes(1): handleSubmit์ด 1๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ

๐Ÿ“Œ 3. Mocking๊ณผ Spying์˜ ์ฐจ์ด์ 

๊ฐœ๋… ์„ค๋ช… ์˜ˆ์ œ
Mocking ํŠน์ • ๋ชจ๋“ˆ์ด๋‚˜ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์งœ๋กœ ๋Œ€์ฒดํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ jest.mock("axios")
Spying ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ ์—ฌ๋ถ€ ๋ฐ ์ธ์ž๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ธฐ๋ฒ• jest.spyOn(console, "log")

๐Ÿ“Œ 4. ๋‹ค์–‘ํ•œ Mocking ์˜ˆ์ œ๋“ค

1๏ธโƒฃ ํŠน์ • ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ธฐ (Mock Implementation)

const mockFn = jest.fn(() => "Hello, Jest!");

console.log(mockFn()); // "Hello, Jest!"
expect(mockFn).toHaveBeenCalled(); // โœ… ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ

 

  • โœ”๏ธ jest.fn(() => "Hello, Jest!") → ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ "Hello, Jest!" ๋ฐ˜ํ™˜

2๏ธโƒฃ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋  ๊ฒฝ์šฐ ๋‹ค๋ฅธ ๊ฐ’ ๋ฐ˜ํ™˜ํ•˜๊ธฐ

const mockFn = jest.fn()
  .mockReturnValueOnce(10)
  .mockReturnValueOnce(20)
  .mockReturnValue(30);

console.log(mockFn()); // 10
console.log(mockFn()); // 20
console.log(mockFn()); // 30
console.log(mockFn()); // 30
  • โœ”๏ธ mockReturnValueOnce() → ์ฒซ ๋ฒˆ์งธ, ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ์—์„œ ๊ฐ๊ฐ 10, 20 ๋ฐ˜ํ™˜
  • โœ”๏ธ ์ดํ›„ ํ˜ธ์ถœ์—์„œ๋Š” mockReturnValue(30)์˜ ๊ฐ’์ด ์ง€์†์ ์œผ๋กœ ๋ฐ˜ํ™˜๋จ

3๏ธโƒฃ ํŠน์ • ๋ชจ๋“ˆ์„ Mocking ํ•˜๊ธฐ

jest.mock("../utils", () => ({
  add: jest.fn(() => 42),
  subtract: jest.fn(() => 10),
}));

import { add, subtract } from "../utils";

test("Mock๋œ add ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ", () => {
  expect(add()).toBe(42); // โœ… add()๊ฐ€ ํ•ญ์ƒ 42 ๋ฐ˜ํ™˜
});

test("Mock๋œ subtract ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ", () => {
  expect(subtract()).toBe(10); // โœ… subtract()๊ฐ€ ํ•ญ์ƒ 10 ๋ฐ˜ํ™˜
});
  • โœ”๏ธ ํŠน์ • ๋ชจ๋“ˆ ๋‚ด ํ•จ์ˆ˜๋“ค์„ Mock ์ฒ˜๋ฆฌํ•˜์—ฌ ๊ณ ์ •๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜

๐Ÿ“Œ 5. Spying ํ™œ์šฉ๋ฒ•

1๏ธโƒฃ ํŠน์ • ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ

const consoleSpy = jest.spyOn(console, "log");

console.log("ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€");

expect(consoleSpy).toHaveBeenCalledWith("ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€"); // โœ… console.log() ํ˜ธ์ถœ ํ™•์ธ
  • โœ”๏ธ jest.spyOn(console, "log") → console.log()๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ์ถ”์ 

2๏ธโƒฃ ์›๋ž˜ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ฉด์„œ ํ˜ธ์ถœ ์—ฌ๋ถ€ ์ถ”์ ํ•˜๊ธฐ

const originalFn = jest.spyOn(Math, "random");

console.log(Math.random()); // ์‹ค์ œ Math.random() ํ˜ธ์ถœ
expect(originalFn).toHaveBeenCalled(); // โœ… ํ˜ธ์ถœ ์—ฌ๋ถ€ ํ™•์ธ
  • โœ”๏ธ jest.spyOn()์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ฉด์„œ ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ ์—ฌ๋ถ€๋งŒ ๊ฒ€์ฆ

3๏ธโƒฃ ํŠน์ • ๋ฉ”์„œ๋“œ์˜ ๋™์ž‘์„ ๊ฐ€๋กœ์ฑ„๊ธฐ (Mock Implementation ์ ์šฉ)

const spy = jest.spyOn(Math, "random").mockReturnValue(0.5);

console.log(Math.random()); // ํ•ญ์ƒ 0.5 ๋ฐ˜ํ™˜
expect(spy).toHaveBeenCalled();
  • โœ”๏ธ ์›๋ž˜ Math.random()์€ 0~1 ์‚ฌ์ด์˜ ๋‚œ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, mockReturnValue(0.5)๋ฅผ ์ ์šฉํ•˜์—ฌ ํ•ญ์ƒ 0.5 ๋ฐ˜ํ™˜

๐Ÿ“Œ 6. Mocking ๋ฐ Spying ๋ชจ๋ฒ” ์‚ฌ๋ก€

โœ… ๋ถˆํ•„์š”ํ•œ Mocking์„ ํ•˜์ง€ ๋ง ๊ฒƒ

  • ์˜ˆ: ๋„ค์ดํ‹ฐ๋ธŒ ํ•จ์ˆ˜(setTimeout, console.log)๋Š” ๊ฐ€๋Šฅํ•˜๋ฉด Mockingํ•˜์ง€ ์•Š๊ธฐ

โœ… ์˜์กด์„ฑ์ด ๋†’์€ ์™ธ๋ถ€ API๋Š” ๋ฐ˜๋“œ์‹œ Mocking

  • API ํ˜ธ์ถœ ์‹œ ์‹ค์ œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์•„๋‹ˆ๋ผ jest.mock()์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ์ˆ˜ํ–‰

โœ… Spy๋Š” ์›๋ž˜ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ

  • ์˜ˆ: console.log์˜ ํ˜ธ์ถœ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด Spy, ์›ํ•˜๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด Mocking

๐Ÿ“Œ 7. ๊ฒฐ๋ก : Jest์—์„œ Mocking & Spying์„ ์ž˜ ํ™œ์šฉํ•˜์ž!

โœ”๏ธ jest.mock()์„ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ๋ชจ๋“ˆ์„ Mock ์ฒ˜๋ฆฌํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธ jest.fn()์„ ํ™œ์šฉํ•˜๋ฉด ํŠน์ • ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธ jest.spyOn()์„ ์ด์šฉํ•ด ํŠน์ • ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ ์—ฌ๋ถ€์™€ ์ธ์ž๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธ ์™ธ๋ถ€ API ์š”์ฒญ์„ Mockingํ•˜๋ฉด ๋„คํŠธ์›Œํฌ ์—†์ด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋” ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

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

728x90
๋ฐ˜์‘ํ˜•