Mock

Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

  • ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ ์‹คํ–‰ ํ™˜๊ฒฝ๊ณผ ์œ ์‚ฌํ• ์ˆ˜๋ก ์žฌํ˜„์„ฑ์ด ๋†’๋‹ค.

  • ํ•˜์ง€๋งŒ ์žฌํ˜„์„ฑ์„ ๋†’์ด๋‹ค๋ณด๋ฉด ์‹คํ–‰ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ฑฐ๋‚˜ ํ™˜๊ฒฝ ๊ตฌ์ถ•์ด ์–ด๋ ค์›Œ์ง€๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

    • ๋Œ€ํ‘œ์ ์œผ๋กœ ์›น API ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ค„์•ผํ•˜๋Š” ๊ฒฝ์šฐ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜์™€๊ฐ™์ด ์ œ์–ดํ•  ์ˆ˜ ์—†๋Š” ๋ถ€๋ถ„์—์„œ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Œ

  • ํ…Œ์ŠคํŠธํ•˜๋Š” ๋Œ€์ƒ์€ ์›น API ์ž์ฒด๊ฐ€ ์•„๋‹Œ ์‘๋‹ต ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ผ๋Š”๊ฒƒ์„ ๋ช…์‹ฌ

  • ์‹ค์ œ API ์‘๋‹ต๊ฐ’ ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ ๊ฐ์ฒด์ด๋‹ค.

  • ๋ชฉ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์šด ๋ถ€๋ถ„์„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ณ  ๋”์šฑ ํšจ์œจ์ ์ธ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

๋ชฉ ๊ฐ์ฒด ์šฉ์–ด

  • stub, spy ๋“ฑ์€ ๋ชฉ ๊ฐ์ฒด๋ฅผ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์„ธ๋ถ„ํ™”ํ•œ ๊ฐ์ฒด์˜ ๋ช…์นญ

  • ๊ฐœ๋ฐœ ์–ธ์–ด์— ์ƒ๊ด€์—†์ด ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๊ด€๋ จ ๋ฌธํ—ˆ์—์„œ ์ •์˜ํ•œ ์šฉ์–ด

Stub

์Šคํ…์€ ์ฃผ๋กœ ๋Œ€์—ญ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. 1) ์˜์กด์ค‘์ธ ์ปดํฌ๋„ŒํŠธ์˜ ๋Œ€์—ญ 2) ์ •ํ•ด์ง„ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์šฉ๋„ 3) ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์— ํ• ๋‹นํ•˜๋Š” ์ž…๋ ฅ๊ฐ’

์ฆ‰, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์‹ค์ œ๋กœ ์˜์กดํ•˜๋Š” ๋ฐ์ดํ„ฐ๋‚˜ ๊ธฐ๋Šฅ์„ ๋Œ€์ฒดํ•˜๋Š” ๋”๋ฏธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• 

  • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์ด ์˜์กด์ค‘์ธ ์ปดํฌ๋„ŒํŠธ์— ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ๋ถ€๋ถ„์ด ์žˆ์„ ๋•Œ

  • ์›น API์— ์˜์กด์ค‘์ธ ๋Œ€์ƒ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒฝ์šฐ

  • "์›น API์—์„œ ์ด๋Ÿฐ ๊ฐ’์„ ๋ฐ˜ํ™˜๋ฐ›์•˜์„ ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ ์ž‘๋™ํ•ด์•ผ ํ•œ๋‹ค." ์™€ ๊ฐ™์€ ํ…Œ์ŠคํŠธ์— ์Šคํ…์„ ์‚ฌ์šฉํ•œ๋‹ค.

Spy

์ŠคํŒŒ์ด๋Š” ์ฃผ๋กœ ๊ธฐ๋กํ•˜๋Š” ์šฉ๋„

1) ํ•จ์ˆ˜๋‚˜ ๋ฉ”์„œ๋“œ์˜ ํ˜ธ์ถœ ๊ธฐ๋ก

2) ํ˜ธ์ถœ๋œ ํšŸ์ˆ˜๋‚˜ ์‹คํ–‰ ์‹œ ์‚ฌ์šฉํ•œ ์ธ์ˆ˜ ๊ธฐ๋ก

3) ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์˜ ์ถœ๋ ฅ ํ™•์ธ

  • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์™ธ๋ถ€์˜ ์ถœ๋ ฅ์„ ๊ฒ€์ฆํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

  • ์ธ์ˆ˜๋กœ ๋ฐ›์€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ

    • ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ ํšŸ์ˆ˜

    • ์‹คํ–‰ ์‹œ ์‚ฌ์šฉํ•œ ์ธ์ˆ˜

    • ์˜๋„ํ•œ ๋Œ€๋กœ ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋๋Š”์ง€ ๊ฒ€์ฆ

Jest์˜ ์šฉ์–ด ํ˜ผ๋ž€

  • ์ œ์ŠคํŠธ์˜ API๋Š” xUnit ํ…Œ์ŠคํŠธํŒจํ„ด ์˜ ์šฉ์–ด ์ •์˜๋ฅผ ์ถฉ์‹คํžˆ ๋”ฐ๋ฅด์ง€ ์•Š๋Š”๋‹ค.

  • ์ œ์ŠคํŠธ๋Š” ์Šคํ…, ์ŠคํŒŒ์ด๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ jest.mock(๋ชฉ ๋ชจ๋“ˆ) ํ˜น์€jest.fn, jest.spyOn(๋ชฉ ํ•จ์ˆ˜) ๋ผ๋Š” API๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  • ์ œ์ŠคํŠธ๋Š” ์ด๋ฅผ ๊ตฌํ˜„ํ•œ ํ…Œ์ŠคํŠธ ๋Œ€์—ญ์„ ๋ชฉ ๊ฐ์ฒด๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๋“ฑ xUnit ํ…Œ์ŠคํŠธ ํŒจํ„ด์—์„œ ์ •์˜ํ•œ ์šฉ์–ด์™€๋Š” ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ๋งŽ์Œ

  • ์Šคํ… ํ˜น์€ ์ŠคํŒŒ์ด๋กœ์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ช…ํ™•ํ•œ ์ด์œ ๊ฐ€ ์žˆ์„ ๋•Œ๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” ๋ชฉ ๊ฐ์ฒด๋ผ๊ณ  ๋ถ€๋ฅด์ž.

Jest๋กœ ๋ชฉ ๋ชจ๋“ˆ์„ ํ™œ์šฉํ•˜์—ฌ ์Šคํ… ๊ตฌํ˜„ํ•˜๊ธฐ

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋‚˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๊ตฌํ˜„์ด ์™„์„ฑ๋˜์–ด ์žˆ์ง€ ์•Š๊ฑฐ๋‚˜ ์ˆ˜์ •์ด ํ•„์š”ํ•œ ๋ชจ๋“ˆ์— ์˜์กด์ค‘์ธ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค

  • ์ด๋•Œ ๋ชฉ ๋ชจ๋“ˆ๋กœ ๋Œ€์ฒดํ•˜๋ฉด ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์—†์—ˆ๋˜ ๋Œ€์ƒ์„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.

export function greet(name: string) {
  return `Hello ${name}.`;
}
export function sayGoodBye(name: string) {
  throw new Error("๋ฏธ๊ตฌํ˜„")
}
import { greet } from './greet';

jest.mock("./greet"); // jest.mock์ด ํ…Œ์ŠคํŠธ ์ „์— ํ˜ธ์ถœ๋˜๋ฉด์„œ ํ…Œ์ŠคํŠธํ•  ๋ชจ๋“ˆ์„ ๋Œ€์ฒดํ•จ

test("์ธ์‚ฌ๋ง์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค(์›๋ž˜ ๊ตฌํ˜„๊ณผ ๋‹ค๋ฅด๊ฒŒ)", () => {
  expect(greet("woong")).toBe(undefined)
})

๋ชจ๋“ˆ์„ ์Šคํ…์œผ๋กœ ๋Œ€์ฒดํ•˜๊ธฐ

  • jest.mock ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜์—๋Š” ๋Œ€์ฒดํ•  ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋ชจ๋“ˆ์˜ ์ผ๋ถ€๋ฅผ ํ…Œ์ŠคํŠธ์—์„œ ๋Œ€์ฒดํ•˜๋ฉด ์˜์กด ์ค‘์ธ ๋ชจ๋“ˆ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ต๋”๋ผ๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

import { greet, sayGoodBye } from './greet';

// ./greet ๋ชจ๋“ˆ์„ ๋ชจ๋‘ ๋Œ€์ฒดํ•˜๊ฒŒ ๋˜์–ด ๋”ฐ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด undefined๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค.
jest.mock("./greet", () => ({
  sayGoodBye: (name: string) => `Good bye, ${name}`,
}))

test("์ž‘๋ณ„ ์ธ์‚ฌ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค(์›๋ž˜ ๊ตฌํ˜„๊ณผ ๋‹ค๋ฅด๊ฒŒ)", () => {
  const message = `${sayGoodBye("woong")} See you.`;
  expect(message).toBe("Good bye. woong See you.");
})

๋ชจ๋“ˆ ์ผ๋ถ€๋ฅผ ์Šคํ…์œผ๋กœ ๋Œ€์ฒดํ•˜๊ธฐ

  • jest.requireActual ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›๋ž˜ ๋ชจ๋“ˆ์˜ ๊ตฌํ˜„์„ import ํ•  ์ˆ˜ ์žˆ๋‹ค.

import { greet, sayGoodBye } from './greet';

jest.mock("./greet", () => ({
  ...jest.requireActual('./greet'),
  sayGoodBye: (name: string) => `Good bye, ${name}.`
}))

test("์ธ์‚ฌ๋ง์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.(์›๋ž˜ ๊ตฌํ˜„๊ณผ ๋™์ผํ•˜๊ฒŒ)", () => {
  expect(greet("woong")).toBe("Hello woong");
})

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋Œ€์ฒดํ•˜๊ธฐ

  • ์ˆ˜์ •์ด ํ•„์š”ํ•œ ๋ชจ๋“ˆ์˜ ์ผ๋ถ€๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์‹ค๋ฌด์—์„œ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋Œ€์ฒดํ•  ๋–„ ๋ชฉ ๋ชจ๋“ˆ์„ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

// next/router ์˜์กด ๋ชจ๋“ˆ ๋Œ€์‹  next-router-mock ์ด๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ ์šฉ
jest.mock("next/router", () => require("next-router-mock"));

์›น API ๋ชฉ ๊ฐ์ฒด ๊ธฐ์ดˆ

  • ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์›น API ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ทจ๋“ํ•˜๊ณ  ๊ฐฑ์‹ ํ•˜๋Š” ์ž‘์—…์€ ํ•„์ˆ˜

  • ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ์›น API ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์›น API ํด๋ผ์ด์–ธํŠธ์˜ ๋Œ€์—ญ์ธ ์Šคํ…์œผ๋กœ ๋Œ€์ฒดํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

  • ์Šคํ…์ด ์‹ค์ œ ์‘๋‹ต์€ ์•„๋‹ˆ์ง€๋งŒ ์‘๋‹ต ์ „ํ›„์˜ ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ๊ฒ€์ฆํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ์›น API ์„œ๋ฒ„๊ฐ€ ์—†์œผ๋ฉด API๋ฅผ ํ˜ธ์ถœ์ด ํฌํ•จ๋œ ๋กœ์ง์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

    • ํ•ด๋‹น API ํ˜ธ์ถœ ๋ถ€๋ถ„์„ ์Šคํ…์œผ๋กœ ๋Œ€์ฒดํ•˜๋ฉด ์‹ค์ œ ์„œ๋ฒ„์˜ ์‘๋‹ต ์—ฌ๋ถ€ ์ƒ๊ด€์—†์ด ๋ฐ์ดํ„ฐ ์ทจ๋“๊ณผ ๊ด€๋ จ๋œ ๋กœ์ง์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

API ์„œ๋ฒ„๋Š” MSW(Mock Service Worker)๋กœ ๋Œ€์ฒดํ•ด์„œ ์‚ฌ์šฉํ•˜์ž.

์•„๋ž˜ ๋‚ด์šฉ๋“ค์€ ๊ทธ๋ƒฅ ์ด๋Ÿฐ๊ฒŒ ์žˆ๊ตฌ๋‚˜ ํ•˜๊ณ  ใ…Œใ…Œใ…Œ

import { getMyProfile } from "../fetchers";

export async function getGreet() {
  const data = await getMyProfile();
  if (!data.name) {
    return `Hello, anonymouse user!`;
  }
  return `Hello, ${data.name}!`;
}

์›น API๋ฅผ ํด๋ผ์ด์–ธํŠธ ์Šคํ… ๊ตฌํ˜„

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์™€ ์ƒ์„ฑ์ด ์ข‹์€ jest.spyOn ์„ ์‚ฌ์šฉํ•ด๋ณด์ž.

import * as Fetchers from "./fetchers";
jest.mock("./fetchers");

// jest.spyOn์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ๊ฐ์ฒด๋ฅผ ๋Œ€์ฒดํ•œ๋‹ค. (ํ…Œ์ŠคํŠธํ•  ๊ฐ์ฒด -> Fetchers)
// jest.spyOn(ํ…Œ์ŠคํŠธํ•  ๊ฐ์ฒด, ํ…Œ์ŠคํŠธํ•  ํ•จ์ˆ˜ ์ด๋ฆ„); 
// *ํ…Œ์ŠคํŠธํ•  ๋Œ€์ƒ์— ์ •์˜๋˜์ง€ ์•Š์€ ํ•จ์ˆ˜ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋ฉด ํƒ€์ž…์˜ค๋ฅ˜ ๋ฐœ์ƒ
jest.spyOn(Fetcehrs, "getMyProfile")

๋ฐ์ดํ„ฐ ์ทจ๋“ ์„ฑ๊ณต์„ ์žฌํ˜„ํ•œ ํ…Œ์ŠคํŠธ

  • ๋ฐ์ดํ„ฐ ์ทจ๋“์ด ์„ฑ๊ณตํ–ˆ์„ ๋•Œ(resolve) ์‘๋‹ต์œผ๋กœ ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ mockResolvedValueOnce ์— ์ง€์ •

  • ์—ฌ๊ธฐ์„œ ์ง€์ •ํ•œ ๊ฐ์ฒด๋„ ํƒ€์ž…์‹œ์Šคํ…œ์ด ์ ์šฉ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ ์œ ์ง€๋ณด์ˆ˜ ์ˆ˜์›”

test("๋ฐ์ดํ„ฐ ์ทจ๋“ ์„ฑ๊ณต์‹œ: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์—†๋Š” ๊ฒฝ์šฐ", async () => {
  // api๊ฐ€ resolve๋์„ ๋•Œ์˜ ๊ฐ’์„ ์žฌํ˜„
  jest.spyOn(Fetchers, "getMyProfile").mockResolvedValueOnce({
    id: "....",
    eamil: "...",
  });
  await expect(getGreet()).resolves.toBe("Hello, anonymouse user!");
})

test("๋ฐ์ดํ„ฐ ์ทจ๋“ ์„ฑ๊ณต์‹œ: ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์žˆ๋Š” ๊ฒฝ์šฐ", async () => {
  // api๊ฐ€ resolve๋์„ ๋•Œ์˜ ๊ฐ’์„ ์žฌํ˜„
  jest.spyOn(Fetchers, "getMyProfile").mockResolvedValueOnce({
    id: "....",
    eamil: "...",
    name: "woong",
  });
  await expect(getGreet()).resolves.toBe("Hello, woong!");
})

๋ฐ์ดํ„ฐ ์ทจ๋“ ์‹คํŒจ๋ฅผ ์žฌํ˜„ํ•œ ํ…Œ์ŠคํŠธ

export function getMyProfile(): Promise<Profile> {
  return fetch("...").then(async (res) => {
    const data = await res.json();
    if (!res.ok) {
      //200๋ฒˆ๋Œ€ ์™ธ์˜ ์‘๋‹ต์ธ ๊ฒฝ์šฐ
      throw data;
    }
    return data;
  })
}

// ์˜ค๋ฅ˜ ๊ฐ์ฒด
export const httpError: HttpError = {
  err: { message: "internal server error" },
};
  • ์˜ค๋ฅ˜ ๊ฐ์ฒด๋ฅผ mockRejectedValueOnce ์ธ์ˆ˜๋กœ getMyProfile ํ•จ์ˆ˜์˜ reject๋ฅผ ์žฌํ˜„ํ•˜๋Š” ์Šคํ…์„ ๊ตฌํ˜„

test("๋ฐ์ดํ„ฐ ์ทจ๋“ ์‹คํŒจ์‹œ", async () => {
  jest.spyOn(Fetchers, "getMyProfile").mockRejectedValueOnce(httpError)
  await expect(getGreet()).rejects.toMatchObject({
    err: {message: "internal server error" },
  })
})

test("๋ฐ์ดํ„ฐ ์ทจ๋“ ์‹คํŒจ์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์˜ˆ์™ธ๊ฐ€ throw๋œ๋‹ค.", async () => {
  expect.assertions(1);
  jest.spyOn(Fetchers, "getMyProfile").mockRejectedValueOnce(httpError)
  try {
    await getGreet();
  } catch (err) {
    expect(err).toMatchObject(httpError);
  }
})

ํ”ฝ์Šค์ฒ˜

์‘๋‹ต์„ ์žฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์˜๋ฏธ

๋ชฉ ๊ฐ์ฒด ์ƒ์„ฑ ํ•จ์ˆ˜

ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ์„ค์ •์„ ์ตœ๋Œ€ํ•œ ์ ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜

  • ํ…Œ์ŠคํŠธํ•  ๋•Œ๋งˆ๋‹ค jest.spyOn ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋˜์–ด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•ด์ง„๋‹ค.

๋ชฉ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ŠคํŒŒ์ด

  • ์ œ์ŠคํŠธ์˜ ๋ชฉ ํ•จ์ˆ˜๋กœ ์ŠคํŒŒ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์ŠคํŒŒ์ด๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์— ๋ฐœ์ƒํ•œ ์ž…์ถœ๋ ฅ์„ ๊ธฐ๋กํ•˜๋Š” ๊ฐ์ฒด

  • ์ŠคํŒŒ์ด์— ๊ธฐ๋ก๋œ ๊ฐ’์„ ๊ฒ€์ฆํ•˜์—ฌ ์˜๋„ํ•œ ๋Œ€๋กœ ๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Œ

์‹คํ–‰๋๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ธฐ

  • jest.fn ์‚ฌ์šฉํ•ด์„œ ๋ชฉ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑ

  • ์ž‘์„ฑํ•œ ๋ชฉ ํ•จ์ˆ˜๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ํ•จ์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ tobeCalled ๋งค์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹คํ–‰ ์—ฌ๋ถ€ ๊ฒ€์ฆ ๊ฐ€๋Šฅ

test("๋ชฉ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋๋‹ค.", () => {
  const mockFn = jest.fn();
  mocFn();
  expect(mockFn).toBeCalled();
})

test("๋ชฉ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋‹ค.", () => {
  const mockFn = jest.fn();
  expect(mockFn).not.toBeCalled();
})

์‹คํ–‰ ํšŸ์ˆ˜ ๊ฒ€์ฆ

  • ๋ชฉ ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ํšŸ์ˆ˜๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.

  • toHaveBeenCalledTimes ๋งค์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ๋ช‡ ๋ฒˆ ํ˜ธ์ถœ๋๋Š”์ง€ ๊ฒ€์ฆ

test("๋ชฉ ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ํšŸ์ˆ˜๋ฅผ ๊ธฐ๋กํ•œ๋‹ค", () => {
  const mockFn = jest.fn();
  mockFn();
  expect(mockFn).toHaveBeenCalledTimes(1);
  mockFn();
  expect(mockFn).toHaveBeenCalledTimes(2);
})

์‹คํ–‰ ์‹œ ์ธ์ˆ˜ ๊ฒ€์ฆ

  • ๋ชฉ ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ์‹œ ์ธ์ˆ˜๋„ ๊ธฐ๋ก

    • toHaveBeenCalledWith ๋งค์ฒ˜๋ฅผ ์‚ฌ์šฉ

  • ๋ชฉ ํ•จ์ˆ˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜ ์•ˆ์— ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

test("๋ชฉ ํ•จ์ˆ˜๋Š” ์•ˆ์—์„œ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.", () => {
  const mockFn = jest.fn();
  function greet() {
    mockFn();
  }
  greet();
  expect(mockFn).toHaveBeenCalledTimes(1);
})
test("๋ชฉ ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ์‹œ ์ธ์ˆ˜๋ฅผ ๊ธฐ๋กํ•œ๋‹ค", () => {
  const mockFn = jest.fn();
  function greet(message: string) {
    mockFn(message); // ์ธ์ˆ˜๋ฅผ ๋ฐ›์•„ ์‹คํ–‰
  }
  greet("hello"); // "hello"๋ฅผ ์ธ์ˆ˜๋กœ ์‹คํ–‰๋œ๊ฒƒ์ด mockFn์— ๊ธฐ๋ก๋œ๋‹ค.
  expect(mockFn).toHaveBeenCalledWith("hello");
})

์‹คํ–‰ ์‹œ ์ธ์ˆ˜๊ฐ€ ๊ฐ์ฒด์ผ ๋•Œ์˜ ๊ฒ€์ฆ

  • ์ธ์ˆ˜๊ฐ€ ๋ฌธ์ž์—ด ๊ฐ™์€ ์›์‹œํ˜•์ด ์•„๋‹Œ ๋ฐฐ์—ด์ด๋‚˜ ๊ฐ์ฒด์ผ ๋•Œ๋„ ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • expect.objectContaining ๋ผ๋Š” ๋ณด์กฐ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ ์ผ๋ถ€๋งŒ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

    • ๊ฐ์ฒด๊ฐ€ ๋„ˆ๋ฌด ํฌ๋ฉด ์ผ๋ถ€๋งŒ ๊ฒ€์ฆํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

test("expect.objectContaining๋ฅผ ์‚ฌ์šฉํ•œ ๋ถ€๋ถ„ ๊ฒ€์ฆ", () => {
  const mockFn = jest.fn();
  checkConfig(mockFn);
  expect(mockFn).toHaveBeenCalledWith(
    expect.objectContaining({
      feature: { spy: true },
    })
  )
})

์‹ค๋ฌด์—์„œ๋Š” 'ํผ์— ํŠน์ • ์ธํ„ฐ๋ ‰์…˜์ด ๋ฐœ์ƒํ•˜๋ฉด ์‘๋‹ต์œผ๋กœ ๋ฐ›์€ ๊ฐ’์€ OO์ด๋‹ค' ๊ฐ™์€ ํ…Œ์ŠคํŠธ๋ฅผ ์ž์ฃผ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

ํ…Œ์ŠคํŠธ ์ค€๋น„

  • ์ž…๋ ฅ ๊ฐ’์„ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜

function inputFactory(input?: Partial<ArticleInput>) {
  return {
    tags: ["testing"],
    title: "ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ…Œ์ŠคํŒ… ์ž‘์„ฑ๋ฒ•",
    body: "...",
    ...input,
  }
}

const input = inputFactory(); //์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ๊ฐ์ฒด ๋ฐ˜ํ™˜
const input = inputFactory({title: "", body: ""}); // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฐ์ฒด ๋ฐ˜ํ™˜

ํ˜„์žฌ ์‹œ๊ฐ์— ์˜์กดํ•˜๋Š” ํ…Œ์ŠคํŠธ

  • ํ˜„์žฌ ์‹œ๊ฐ์— ์˜์กดํ•˜๋Š” ๋กœ์ง์ด ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์— ํฌํ•จ๋๋‹ค๋ฉด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ์‹คํ–‰ ์‹œ๊ฐ์— ์˜์กดํ•˜๊ฒŒ ๋จ

  • ํŠน์ • ์‹œ๊ฐ„๋Œ€์—๋Š” CI์˜ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๊ฐ€ ์‹คํŒจํ•˜๋Š” ๋ถˆ์•ˆ์ •ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋œ๋‹ค.

  • ์ด๋•Œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ™˜๊ฒฝ์— ํ˜„์žฌ ์‹œ๊ฐ์„ ๊ณ ์ •ํ•˜๋ฉด ํ•ญ์ƒ ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

export function greetByTime() {
  const hour = new Date().getHours();
  if (hour < 12) {
    return "good morning"
  } else if (hour < 18) {
    return "good afternoon",
  } else { 
    return "good evening"
  }
}

ํ˜„์žฌ ์‹œ๊ฐ ๊ณ ์ •ํ•˜๊ธฐ

  • ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ™˜๊ฒฝ์˜ ํ˜„์žฌ ์‹œ๊ฐ์„ ์ž„์˜์˜ ์‹œ๊ฐ์œผ๋กœ ๊ณ ์ •ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜ ์‚ฌ์šฉ

    • jest.useFakeTimers : ์ œ์ŠคํŠธ์— ๊ฐ€์งœ ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ง€์‹œ

    • jest.setSystemTime : ๊ฐ€์งœ ํƒ€์ด๋จธ์—์„œ ์‚ฌ์šฉํ•  ํ˜„์žฌ ์‹œ๊ฐ์„ ์„ค์ •

    • jest.useRealTimers : ์ œ์ŠคํŠธ์— ์‹ค์ œ ํƒ€์ด๋จธ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์ง€์‹œํ•˜๋Š” ์›์ƒ ๋ณต๊ท€ ํ•จ์ˆ˜

beforeEach, afterEach ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ์‚ฌ์ „ ์„ค์ • ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

describe("greetByTime(", () => {
  beforeEach(() => {
    jest.useFakeTimers();
  })
  
  afterEach(() => {
    jest.useRealTimers();
  })
  
  test("์•„์นจ์—๋Š” 'good morning'์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค", () => {
    jest.setSystemTime(new Date(2023, 4, 23, 8, 0, 0));
    expect(greetByTime()).toBe("good morning")
  })
})

ํ…Œ์ŠคํŠธ ์„ค์ •๊ณผ ํŒŒ๊ธฐ

  • ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „ ๊ณตํ†ต์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•  ์ž‘์—…์ด ์žˆ๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„์— ๊ณตํ†ต์œผ๋กœ ํŒŒ๊ธฐํ•˜๊ณ  ์‹ถ์€ ์ž‘์—…์ด ์žˆ๋Š” ๊ฒฝ์šฐ

  • ์„ค์ • ์ž‘์—…์„ beforeAll , beforeEach ๋ฅผ

  • ํŒŒ๊ธฐ ์ž‘์—…์„ afterAll, afterEach ๋ฅผ ์‚ฌ์šฉ

beforeAll(() => console.log("1 - beforeAll"));
afterAll(() => console.log("1 - afterAll"));
beforeEach(() => console.log("1 - beforeEach"));
afterEach(() => console.log("1 - afterEach"));

test("", () => console.log("1 - test"));

describe("Scoped / Nested block", () => {
  beforeAll(() => console.log("2 - beforeAll"));
  afterAll(() => console.log("2 - afterAll"));
  beforeEach(() => console.log("2 - beforeEach"));
  afterEach(() => console.log("2 - afterEach")
  
  test("", () => console.log("2 - test"));
})

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll

Last updated