Skip to content

[1ํŒ€ ์‹ ํฌ์›] Chapter ๐Ÿงฆ 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ#42

Open
Amelia-Shin wants to merge 35 commits intohanghae-plus:mediumfrom
Amelia-Shin:medium
Open

[1ํŒ€ ์‹ ํฌ์›] Chapter ๐Ÿงฆ 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ#42
Amelia-Shin wants to merge 35 commits intohanghae-plus:mediumfrom
Amelia-Shin:medium

Conversation

@Amelia-Shin
Copy link

@Amelia-Shin Amelia-Shin commented Aug 19, 2025

Medium

7์ฃผ์ฐจ ๊ณผ์ œ ์ฒดํฌํฌ์ธํŠธ

๊ธฐ๋ณธ๊ณผ์ œ

Medium

  • ์ด 11๊ฐœ์˜ ํŒŒ์ผ, 115๊ฐœ์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฌด์‚ฌํžˆ ์ž‘์„ฑํ•˜๊ณ  ํ†ต๊ณผ์‹œํ‚จ๋‹ค.

์งˆ๋ฌธ

Q. medium.useEventOperations.spec.tsx > ์•„๋ž˜ toastFn๊ณผ mock๊ณผ ์ด fn์€ ๋ฌด์—‡์„ ํ•ด์ค„๊นŒ์š”?

toastFn : ์‹ค์ œ ํ† ์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๊ณ , ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€๋งŒ ๊ธฐ๋ก
mock (notistack) : ์‹ค์ œ notistack ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†์–ด๋„ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
const enqueueSnackbarFn = vi.fn(); : ๊ฐ€์งœ ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœ์„ ์ถ”์ ํ•˜๊ณ  ๊ธฐ๋ก

Q. medium.integration.spec.tsx > ์—ฌ๊ธฐ์„œ ChakraProvider๋กœ ๋ฌถ์–ด์ฃผ๋Š” ๋™์ž‘์€ ์˜๋ฏธ์žˆ์„๊นŒ์š”? ์žˆ๋‹ค๋ฉด ์–ด๋–ค ์˜๋ฏธ์ผ๊นŒ์š”?

ThemeProvider (ํ…Œ๋งˆ ์ œ๊ณต)
SnackbarProvider (ํ† ์ŠคํŠธ ์‹œ์Šคํ…œ)
CssBaseline (๊ธฐ๋ณธ ์Šคํƒ€์ผ ์ดˆ๊ธฐํ™”)

์ปดํฌ๋„ŒํŠธ๋“ค์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๊ธฐ ์œ„ํ•จ (Provider๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง์ด ๋˜์ง€๋งŒ ๊ธฐ๋Šฅ์€ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ)

Q. handlersUtils > ์•„๋ž˜ ์—ฌ๋Ÿฌ๊ฐ€์ง€ use ํ•จ์ˆ˜๋Š” ์–ด๋–ค ์—ญํ• ์„ ํ• ๊นŒ์š”? ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์„๊นŒ์š”?

๊ฐ ์ด๋ฒคํŠธ ์ƒ์„ฑ/์ˆ˜์ •/์‚ญ์ œ๋ฅผ ์œ„ํ•œ mock API๋ฅผ ์„ค์ •ํ•˜์—ฌ, ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „์— ํ•„์š”ํ•œ mock ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ• ์ˆ˜์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฏธ ์งœ์—ฌ์ ธ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ๋‹ค ์“ฐ๋Š”๊ฑฐ์˜€์ง€๋งŒ ๋ฏธ๋””์›€์—์„œ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค..
ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ์‹ค์ œ API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ์— ๋„ˆ๋ฌด ๋งŽ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค๊ณ , ์‹ค์ œ ์„œ๋ฒ„ ๋Œ€์‹  ๊ฐ€์งœ ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋” ๋น ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

Q. setupTests.ts > ์™œ ์ด ์‹œ๊ฐ„์„ ์„ค์ •ํ•ด์ฃผ๋Š” ๊ฑธ๊นŒ์š”?

vi.setSystemTime์œผ๋กœ ๊ณ ์ •๋œ ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜์—ฌ ๋‚ ์งœ/์‹œ๊ฐ„ ์˜์กด ๋กœ์ง์ด ํ•ญ์ƒ ์ผ๊ด€๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
์žฅ์  : ํ…Œ์ŠคํŠธ์˜ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ๊ณผ ์žฌํ˜„์„ฑ์„ ๋ณด์žฅ!

์‹ฌํ™” ๊ณผ์ œ

  • App ์ปดํฌ๋„ŒํŠธ ์ ์ ˆํ•œ ๋‹จ์œ„์˜ ์ปดํฌ๋„ŒํŠธ, ํ›…, ์œ ํ‹ธ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ–ˆ๋Š”๊ฐ€?
  • ํ•ด๋‹น ๋ชจ๋“ˆ๋“ค์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํ…Œ์ŠคํŠธ๋ฅผ 5๊ฐœ ์ด์ƒ ์ž‘์„ฑํ–ˆ๋Š”๊ฐ€?

๊ณผ์ œ ์…€ํ”„ํšŒ๊ณ 

TMI. Hard ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ ์ปค๋ฐ‹ํ•˜์ง€ ์•Š์€ ํŒŒ์ผ๋“ค์ด ๋‹ค ๋‚ ๋ผ๊ฐ”์–ด์š”..
์™œ ๋‚ ๋ผ๊ฐ”๋ƒ๋ฉด.... ํ‘ธ์‰ฌ๋ฅผ ํ–ˆ๋”๋‹ˆ off์ฝ”์น˜๋‹˜ ์ฝ”๋“œ์— ํ‘ธ์‰ฌ๊ฐ€ ๋˜์—ˆ๊ณ , ์•„๋ž˜ ๋ช…๋ น์–ด ์‹คํ–‰ํ–ˆ๋”๋‹ˆ ์ˆ˜์ •ํ•œ ํŒŒ์ผ๋“ค์ด ๋‹ค ๋‚ ๋ผ๊ฐ”์–ด์š” ^0^
์Šคํ…Œ์ด์ง•์— ์•ˆ์˜ฌ๋ ค๋‘” ์ œ ์ž˜๋ชป.... ์•ž์œผ๋กœ reset ์กฐ์‹ฌํ•˜์ž....

# 1. ๋กœ์ปฌ์—์„œ ๋งˆ์ง€๋ง‰ ์ปค๋ฐ‹๊ณผ ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋ชจ๋‘ ์‚ญ์ œ (hard reset)
git reset --hard HEAD~1

# 2. ์›๊ฒฉ์—์„œ๋„ ์‚ญ์ œ
git push origin +HEAD

๊ฐ‘์ž๊ธฐ ์˜์š• ๋‹ค ๊บพ์ด๊ณ  ์‹œ๊ฐ„๋ฌธ์ œ๋กœ ๋ฏธ๋””์›€์œผ๋กœ ๋‚ด๋ ค์™”์Šต๋‹ˆ๋‹ค... (๊ทธ๋ž˜๋„...๊ณผ์ œ 2๋ฒˆ ๋ฐ˜๋ณตํ•™์Šตํ• ์ˆ˜์žˆ์–ด์„œ ์ข‹-์•„- ^o^)

๊ธฐ์ˆ ์  ์„ฑ์žฅ

  • ์—๋Ÿฌ ๋‚œ๋‹ค๊ณ  AIํ•œํ…Œ๋งŒ ์˜์กดํ•˜์ง€๋ง์ž...

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

์ฒ˜์Œ์— ์ง  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

import { render, screen } from '@testing-library/react';

/* ์ด๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ EMFILE ์—๋Ÿฌ ๋ฐœ์ƒ X
// MUI Icons ๊ฐ„๋‹จ ๋ชจํ‚น
vi.mock('@mui/icons-material', () => ({
  ChevronLeft: () => null,
  ChevronRight: () => null,
  Notifications: () => null,
}));
*/

import { CalendarView } from '../../components/calendar/CalendarView';
import { createMockEvents } from '../utils';

const renderCalendarView = (props: Parameters<typeof CalendarView>[0]) =>
  render(<CalendarView {...props} />);

describe('CalendarView', () => {
  const mockEvents = createMockEvents();

  const mockHolidays = {
    '2025-01-01': '์‹ ์ •',
    '2025-01-15': '์„ค๋‚ ',
  };

  const mockSetView = vi.fn();
  const mockNavigate = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
  });
  it('Week์ด ์„ ํƒ๋˜์—ˆ์„ ๋•Œ ํ•ด๋‹น ๋‚ ์งœ์˜ ์ฃผ๊ฐ„ ์ผ์ •์ด ๋ Œ๋”๋ง๋œ๋‹ค', () => {
    renderCalendarView({
      view: 'week',
      setView: mockSetView,
      currentDate: new Date('2025-01-15'),
      navigate: mockNavigate,
      filteredEvents: mockEvents,
      notifiedEvents: [],
      holidays: mockHolidays,
    });

    expect(screen.getByTestId('week-view')).toBeInTheDocument();
    expect(screen.queryByTestId('month-view')).not.toBeInTheDocument();
  });

๊ฒฐ๊ตญ ๊ตฌ๊ธ€๋ง์„ ํ•ด์„œ ์ฐพ์€ ๋ฐฉ๋ฒ•!!!
mui/material-ui#46324

-import { LocationCity } from "@mui/icons-material";
+import LocationCity from "@mui/icons-material/LocationCity";

๋ช‡์‹œ๊ฐ„๋™์•ˆ ์‚ฝ์งˆํ–ˆ๋Š”๋ฐ.... ๋‹ค์Œ๋ถ€ํ„ฐ ์ ˆ๋Œ€ ๋˜‘๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋ณด๋”๋ผ๋„ AIํ•œํ…Œ ๊ณ ์ณ๋‹ฌ๋ผ๋Š”๋‘ฅ ์‹œ๊ฐ„๋‚ญ๋น„ํ•˜์ง€๋ง์ž.

์ฝ”๋“œ ํ’ˆ์งˆ

์—†์Šต๋‹ˆ๋‹ค.

ํ•™์Šต ํšจ๊ณผ ๋ถ„์„

  • act ์™€ waitFor
    act, waitFor ๊ฐ™์€ ๋„๊ตฌ๊ฐ€ ๋น„๋™๊ธฐ ๋กœ์ง๊ณผ UI ์—…๋ฐ์ดํŠธ๋ฅผ ์ œ์–ดํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

act ์™€ waitFor์˜ ์ฐจ์ด์ 

act๋Š” ๋™๊ธฐ, ๋น„๋™๊ธฐ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
waitFor์€ ํ•ญ์ƒ ๋น„๋™๊ธฐ

act์™€ waitFor์€ ์–ธ์ œ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ข‹์„๊นŒ?

// ๋™๊ธฐ act - ํƒ€์ด๋จธ ์ฆ‰์‹œ ์ง„ํ–‰
act(() => {
   vi.advanceTImersByTime(1000);
});

// waitFor - ์‹ค์ œ๋กœ ์‹œ๊ฐ„์ด ํ˜๋Ÿฌ์•ผ ํ•จ
  await waitFor(() => {
    expect(screen.getByText('1์ดˆ ์ง€๋‚จ')).toBeInTheDocument();
  }, { timeout: 2000 });

๊ณผ์ œ ํ”ผ๋“œ๋ฐฑ

easy ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๋„ ์žฌ๋ฐŒ์—ˆ๊ณ , ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋Š” ์ฒ˜์Œ์— ์ข€ ๋ง‰๋ง‰ํ–ˆ์ง€๋งŒ ๋ฐฉ๋ฒ•์„ ์•Œ๊ณ  ํ•˜๋‚˜์”ฉ ํ•ด๋ณด๋‹ˆ ์˜คํžˆ๋ ค easy๋ณด๋‹ค ๋” ์žฌ๋ฐŒ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฆฌ๋ทฐ ๋ฐ›๊ณ  ์‹ถ์€ ๋‚ด์šฉ

Q1. ์ €๋Š” ์•„๋ž˜ ์ฝ”๋“œ์—์„œ 2๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™”๋Š”๋ฐ์š”.
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ๋ฒ• ์ค‘์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋„ ์žˆ๋Š”๋ฐ ๋ณดํ†ต 1๋ฒˆ ๋ฐฉ๋ฒ•์€ ์–ธ์ œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

it('์ €์žฅ๋˜์–ด์žˆ๋Š” ์ดˆ๊ธฐ ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ๋ถˆ๋Ÿฌ์˜จ๋‹ค', async () => {
  setupMockHandlerCreation(events);
  const { result } = renderHook(() => useEventOperations(false));
 
//  1๋ฒˆ ๋ฐฉ๋ฒ•
   await act(async () => {
     await new Promise((resolve) => setTimeout(resolve, 0));
   });

// 2๋ฒˆ ๋ฐฉ๋ฒ•
  await act(async () => {
    await result.current.fetchEvents();
  });

  expect(result.current.events).toEqual(events);
  expect(enqueueSnackbarFn).toHaveBeenCalledWith('์ผ์ • ๋กœ๋”ฉ ์™„๋ฃŒ!', { variant: 'info' });
});

Q2. ์˜๋ฏธ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ค€์ด ์–ด๋–ค๊ฒƒ์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.
๋‹จ์ผ ํ…Œ์ŠคํŠธ์—์„œ ๊ตณ์ด ์ด๋Ÿฐ ๋‚ด์šฉ๊นŒ์ง€ ํ…Œ์ŠคํŠธ์— ๋„ฃ์–ด์•ผํ•˜๋‚˜? ์‹ถ์—ˆ๋˜๊ฒŒ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ์‹œ)

  it.skip('์œ ํšจํ•˜์ง€ ์•Š์€ ์›”์— ๋Œ€ํ•ด ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•œ๋‹ค', () => {
    expect(getDaysInMonth(2025, 13)).toBe(31);
  });

it.skip('size ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒ๋žตํ•˜๋ฉด ๊ธฐ๋ณธ๊ฐ’ 2๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค', () => {
    expect(fillZero(1)).toBe('01');
  });

๊ทธ๋ฆฌ๊ณ  ์˜๋ฏธ์žˆ๋Š” ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„  ์–ด๋–ค๊ฑธ ํ•˜๋ฉด ์ข‹์„๊นŒ์š”?

@Amelia-Shin Amelia-Shin changed the title ์‹ ํฌ์› 1ํŒ€ ์‹ ํฌ์›] Chapter ๐Ÿงฆ 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ Aug 19, 2025
@Amelia-Shin Amelia-Shin changed the title 1ํŒ€ ์‹ ํฌ์›] Chapter ๐Ÿงฆ 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ [1ํŒ€ ์‹ ํฌ์›] Chapter ๐Ÿงฆ 3-1. ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๐Ÿงฆ Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant