๐ญ 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ํ๋ฉด ๋คํธ์ํฌ ์์ด ํ ์คํธ ๊ฐ๋ฅํ๋ฉฐ, ๋ ์ ๋ขฐ์ฑ ์๋ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ๋ค.
๐ท ์ ์ค์ ๊ฐ๋ฐ์๊ฐ ๋์ด๋ด ์๋น! ๐ท ๐