๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

10/8/2025

๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ์„ ์ •ํ•˜๊ธฐ

์ €๋ฒˆ์‹œ๊ฐ„์— ๋ณด์•˜๋˜ ๋‹จ์œ„ํ…Œ์ŠคํŠธ๊ฐ€ ๋ฌด์—‡์ธ๊ฐ€ ๋Œ์•„๋ณด์ž๋ฉด, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ์•„๋‹Œ ๋‹จ์ผ ์ปดํฌ๋„ŒํŠธ๋ฅผ, ๊ทธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ…Œ์ŠคํŠธ์ด๋‹ค.
๊ทธ๋ž˜์„œ Atomic ์ปดํฌ๋„ŒํŠธ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ๊ฒƒ๋“ค์ด ๋‹จ์œ„ํ…Œ์ŠคํŠธ์˜ ์ฃผ ๋Œ€์ƒ์ด๋ผ๊ณ  ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฐ˜๋ฉด ๋˜ ๋„ˆ๋ฌด ๋‹จ์ˆœํ•œ ๊ฒฝ์šฐ(๋‹จ์ˆœํ•œ ui ๋ Œ๋”๋ง, ๊ฐ„๋‹จํ•œ ๋กœ์ง)์—๋Š” ์ผ์ผ์ด ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑฐ์น  ํ•„์š” ์—†์ด ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋“ค๊ณผ ํ•จ๊ป˜ (์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ) ํ†ตํ•ฉํ…Œ์ŠคํŠธ์—์„œ ๊ฒ€์ฆํ•˜๋Š”๊ฒƒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•˜๋‹ค.

๋ฆฌ์•กํŠธ ํ›…, ์œ ํ‹ธํ•จ์ˆ˜

์ปดํฌ๋„ŒํŠธ ์™ธ์—๋„ ๋ฆฌ์•กํŠธ ํ›…๊ณผ ์œ ํ‹ธ ํ•จ์ˆ˜๋„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์˜ ๋Œ€์ƒ์ด ๋œ๋‹ค. (๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•˜๊ธฐ ๋งค์šฐ ์ ํ•ฉํ•˜๋‹ค)

์œ ํ‹ธ ํ•จ์ˆ˜๋Š” ๋ณดํ†ต ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋“ค์ด๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•๋“ค์„ ๊ฐ€์ง€๊ณ ์žˆ๋‹ค :

  • ์ˆœ์ˆ˜ ํ•จ์ˆ˜ : ํ•จ์ˆ˜ ์™ธ๋ถ€์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋“ฑ, ์™ธ๋ถ€์™€ ์ƒํ˜ธ์‚ญ์šฉ ํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜. (๋ถˆ๋ณ€ ์ƒ์ˆ˜ ๋‹จ์ˆœ ์ฐธ์กฐ๋Š” ์ œ์™ธ)
  • UI ๋กœ์ง๊ณผ ๋ฌด๊ด€
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง : ๊ณ„์‚ฐ, ๋ณ€ํ™˜, ๊ฒ€์ฆ ๋“ฑ์˜ ์ˆœ์ˆ˜ํ•œ ๋กœ์ง

๋ชจ๋“ˆ ๋ชจํ‚น

shopping-mall-unit-test ๋ธŒ๋žœ์น˜์—์„œ EmptyNotice์™€ NotFoundPage,ErrorPage ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•œ๋‹ค.

  • EmptyNotice : ์žฅ๋ฐ”๊ตฌ๋‹ˆ๊ฐ€ ๋น„์–ด์žˆ๋Š” ์ƒํƒœ์—์„œ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œ์— ์žฅ๋ฐ”๊ตฌ๋‹ˆ๊ฐ€ ๋น„์–ด์žˆ๋‹ค๊ณ  ๋ณด์—ฌ์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ
    • ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์‚ฌ์‹ค ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์กฐํ•ฉ๋˜์–ด ์ด๋ฃจ์–ด์ง„ ์ปดํฌ๋„ŒํŠธ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ™ˆ์œผ๋กœ ์ด๋™ํ•œ๋‹ค๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋…๋ฆฝ์ ์ธ ์ปดํฌ๋„ŒํŠธ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • NotFoundPage : ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป๋œ ๊ฒฝ๋กœ๋กœ ์ด๋™ํ–ˆ์„ ๋•Œ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€
  • ErrorPage : ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€
    • ๋‘ ์—๋Ÿฌ ํŽ˜์ด์ง€ ๋ชจ๋‘ ํŽ˜์ด์ง€ ๋‹จ์œ„์˜ ํฐ ์ปดํฌ๋„ŒํŠธ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๊ต‰์žฅํžˆ ๋‹จ์ˆœํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ฐ€์ง€๊ณ ์žˆ๊ณ  ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ '๋’ค๋กœ ์ด๋™'๊ณผ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์œ„ ์„ธ ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘ react-router-dom์˜ useNavigate()ํ›…์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•˜๊ณ ์žˆ๋‹ค. (์ฆ‰ react-router-dom์— ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.)
์ด ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ• ๋•Œ๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ํ˜ธ์ถœํ•˜๋Š”์ง€๋งŒ ๊ฒ€์ฆํ•˜๋ฉด ๋œ๋‹ค. (์™ธ๋ถ€ ๋ชจ๋“ˆ์˜ ๊ฒ€์ฆ์€ ํ•ด๋‹น ๋ชจ๋“ˆ ์ž์ฒด์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆํ•  ์ผ์ด์ง€ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ์—ฌ๊ธฐ์„œ ํ•  ์ผ์€ ์•„๋‹˜)
๊ทธ๋ฆฌ๊ณ  ์ด ์™ธ๋ถ€ ๋ชจ๋“ˆ์˜ ํŠน์ • ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ํ˜ธ์ถœํ•˜๋Š”์ง€ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด ๋ชจํ‚น์ด๋ผ๋Š” ๊ฒƒ์„ ํ•œ๋‹ค.

๋ชจํ‚น : ์‹ค์ œ ๋ชจ๋“ˆ,๊ฐ์ฒด์™€ ๋™์ผํ•œ ๋™์ž‘์„ ํ•˜๋„๋ก ๋งŒ๋“  ๋ชจ์˜ ๋ชจ๋“ˆ,๊ฐ์ฒด๋กœ ์‹ค์ œ๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ.

  • ๋ชจํ‚น์„ ํ†ตํ•ด ์šฐ๋ฆฌ๋Š” ์™ธ๋ถ€ ๋ชจ๋“ˆ๊ณผ ๊ฒ€์ฆํ•  ๋ชจ๋“ˆ์„ ๋ถ„๋ฆฌํ•ด์„œ ํ•„์š”ํ•œ ๊ฒ€์ฆ๋งŒ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ ๋ชจ๋“ˆ๊ณผ ์™„์ „ํžˆ ๋™์ผํ•œ ๋ชจ์˜ ๊ฐ์ฒด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ํฐ ๋น„์šฉ์ด ๋“œ๋Š” ์ž‘์—…์ด๋ฉฐ, (์ •ํ™•ํžˆ ๋ฌด์Šจ๋œป์ผ๊นŒ?)
  • ๋ชจ์˜ ๊ฐ์ฒด๋ฅผ ๋‚จ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ…Œ์ŠคํŠธ ์‹ ๋ขฐ์„ฑ์„ ๋‚ฎ์ถ”๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋‚ณ๋Š”๋‹ค.
const navigateFn = vi.fn();

vi.mock('react-router-dom', async () => {
  const original = await vi.importActual('react-router-dom');

  return { ...original, useNavigate: () => navigateFn };
});

it('Home์œผ๋กœ ์ด๋™ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ํ™ˆ ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•˜๋Š” navigate๊ฐ€ ์‹คํ–‰๋œ๋‹ค', async () => {
  const { user } = await render(<NotFoundPage />);

  const button = await screen.getByRole('button', { name: 'Home์œผ๋กœ ์ด๋™' });

  await user.click(button);

  expect(navigateFn).toHaveBeenNthCalledWith(1, '/', { replace: true });
});

vi.mock()ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด react-router-dom์˜ ๋ชจํ‚น์„ ๋งŒ๋“ ๋‹ค.
๊ทธ๋Ÿฌ๋ฉด <NotFoundPage /> ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•ด๋‹น ๋ชจ๋“ˆ์€ ์ง„์งœ ๋ชจ๋“ˆ์ด ์•„๋‹Œ ์—ฌ๊ธฐ์„œ ๋งŒ๋“ค์–ด์ง„ ๋ชจํ‚น์ด ๋œ๋‹ค.
๊ทธ ์ปดํฌ๋„ŒํŠธ์—์„œ useNavigate()๋ฅผ ํ˜ธ์ถœํ•  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜ธ์ถœํ•  ๋•Œ

  const handleClickNavigateHomeButton = () => {
    navigate(pageRoutes.main, { replace: true });
  };
  
  return(
  ...
  <button onClick={handleClickNavigateHomeButton}>Home์œผ๋กœ ์ด๋™</button>
  )

์‹ค์ œ๋กœ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์€ ๋ชจํ‚น์— ๋„ฃ์—ˆ๋˜ navigateFn ์ŠคํŒŒ์ด ํ•จ์ˆ˜๋‹ค.
๊ทธ๋ž˜์„œ expect()๋งค์ฒ˜๋กœ useNavigate()ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ ์–ด๋–ค ์ธ์ž๋ฅผ ๋„ฃ์—ˆ๋Š”์ง€ ("/", {replace : true})ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

๋ชจํ‚น ์ดˆ๊ธฐํ™”

์œ„์—์„œ useNavigate()์™€ react-router-dom ๋ชจ๋“ˆ์„ ๋ชจํ‚นํ–ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์ด ๋ชจํ‚น์ด ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ(๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๋ชจํ‚น์„ ํ•ด์•ผํ•œ๋‹ค๋˜๊ฐ€)๋ฅผ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ํ›„์—๋Š” ๋ชจํ‚น์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š”๊ฒƒ์ด ์ข‹๋‹ค.

//setupTests.js
...

beforeAll(() => {
  server.listen();
});

afterEach(() => {
  server.resetHandlers();
  vi.clearAllMocks();
});

afterAll(() => {
  vi.resetAllMocks();
  server.close();
});

...

clearAllMocks() :

  • ๋ชจํ‚น๋œ ๋ชจ์˜ ๊ฐ์ฒด ํ˜ธ์ถœ์— ๋Œ€ํ•œ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”
    • ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๊ณ  ๊ณ„์† ์Œ“์ด๋ฉด ํ˜ธ์ถœ ํšŸ์ˆ˜๋‚˜ ์ธ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ชจํ‚น๋œ ๋ชจ๋“ˆ์˜ ๊ตฌํ˜„์„ ์ดˆ๊ธฐํ™” ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค (๋ชจํ‚น๋œ ์ƒํƒœ๋กœ ์œ ์ง€)

resetAllMocks() :

  • ๋ชจํ‚น๋œ ๋ชจ๋“ˆ์˜ ๊ตฌํ˜„์„ ์ดˆ๊ธฐํ™”