YeongseoYoon-hanghae λ‹˜μ˜ μƒμ„ΈνŽ˜μ΄μ§€ > [5νŒ€ μœ€μ˜μ„œ] Chapter 🐷 3-1. ν”„λ‘ νŠΈμ—”λ“œ ν…ŒμŠ€νŠΈ μ½”λ“œ 🐷

HARD

7주차 과제 체크포인트

기본과제

  • 총 11개의 파일, 115개의 λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό λ¬΄μ‚¬νžˆ μž‘μ„±ν•˜κ³  ν†΅κ³Όμ‹œν‚¨λ‹€.

질문

Q. handlersUtils에 남긴 μ§ˆλ¬Έμ— λ‹΅λ³€ν•΄μ£Όμ„Έμš”. // ! Hard // ! μ΄λ²€νŠΈλŠ” 생성, μˆ˜μ • 되면 fetchλ₯Ό λ‹€μ‹œ ν•΄ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈ ν•©λ‹ˆλ‹€. 이λ₯Ό μœ„ν•œ μ œμ–΄κ°€ ν•„μš”ν•  것 κ°™μ€λ°μš”. μ–΄λ–»κ²Œ μž‘μ„±ν•΄μ•Ό ν…ŒμŠ€νŠΈκ°€ λ³‘λ ¬λ‘œ λŒμ•„λ„ μ•ˆμ •μ μ΄κ²Œ λ™μž‘ν• κΉŒμš”? // ! μ•„λž˜ 이름을 μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„ λ˜λ‹ˆ, λ…λ¦½μ μ΄κ²Œ ν…ŒμŠ€νŠΈλ₯Ό ꡬ동할 수 μžˆλŠ” 방법을 μ°Ύμ•„λ³΄μ„Έμš”. 그리고 이 λ‘œμ§μ„ PR에 μ„€λͺ…ν•΄μ£Όμ„Έμš”.

useEventOperationsλ₯Ό 확인해보면 saveEvent, deleteEventμ—μ„œ 처리 후에 fetchEventλ₯Ό 톡해 μƒˆλ‘œ 이벀트λ₯Ό 뢈러였게 λ˜λŠ”λ°μš”. 이 κ²½μš°μ— μ‚­μ œ, μˆ˜μ •, μΆ”κ°€ μ΄λ²€νŠΈμ— λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ λ³‘λ ¬λ‘œ μ²˜λ¦¬λ˜μ—ˆμ„ κ²½μš°μ— 데이터가 λΆˆμ•ˆμ •ν•  κ²ƒμœΌλ‘œ νŒλ‹¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ

// setupMockHandlerUpdating - μˆ˜μ • ν…ŒμŠ€νŠΈμš©
export const setupMockHandlerUpdating = () => {
  const events: Event[] = [
    {
      id: '1',
      title: 'μˆ˜μ •ν…ŒμŠ€νŠΈμž…λ‹ˆλ‹Ή',
      date: '2025-10-17',
      // ... κΈ°λ³Έ 데이터
    }
  ];

  server.use(
    http.put('/api/events/:id', async ({ params, request }) => {
      const updatedEvent = (await request.json()) as Event;
      const index = events.findIndex((event) => event.id === params.id);

      if (index === -1) {
        return new HttpResponse(null, { status: 404 }); 
      }

      events[index] = { ...events[index], ...updatedEvent };
      return HttpResponse.json(events[index]);
    })
  );
};

// setupMockHandlerDeletion - μ‚­μ œ ν…ŒμŠ€νŠΈμš©  
export const setupMockHandlerDeletion = () => {
  const events: Event[] = [/* μ‚­μ œμš© κΈ°λ³Έ 데이터 */];
  
  server.use(
    http.delete('/api/events/:id', ({ params }) => {
      const index = events.findIndex((event) => event.id === params.id);

      if (index === -1) {
        return new HttpResponse(null, { status: 404 });
      }

      events.splice(index, 1); 
      return new HttpResponse(null, { status: 204 });
    })
  );
};

μœ„μ™€ 같이 MSW ν•Έλ“€λŸ¬ 자체의 ꡬ쑰λ₯Ό κ°œμ„ ν–ˆμŠ΅λ‹ˆλ‹€. κΈ°μ‘΄μ—λŠ” ν•˜λ‚˜μ˜ μ „μ—­ 배열을 μ—¬λŸ¬ ν…ŒμŠ€νŠΈκ°€ κ³΅μœ ν•˜λŠ” ν˜•νƒœμ˜€λ‹€λ©΄, μ΄μ œλŠ” 각 ν•Έλ“€λŸ¬ μ„€μ • ν•¨μˆ˜κ°€ 호좜될 λ•Œλ§ˆλ‹€ μ™„μ „νžˆ μƒˆλ‘œμš΄ 배열을 μƒμ„±ν•˜λ„λ‘ λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€. setupMockHandlerCreation ν•¨μˆ˜μ—μ„œλŠ” 전달받은 초기 데이터λ₯Ό 방어적 볡사λ₯Ό 톡해 독립적인 λ°°μ—΄λ‘œ λ§Œλ“€κ³ , 이 λ°°μ—΄λ§Œμ„ μ‚¬μš©ν•˜λŠ” GET, POST ν•Έλ“€λŸ¬λ₯Ό μ„€μ •ν•˜λ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

Q. ν…ŒμŠ€νŠΈλ₯Ό λ…λ¦½μ μœΌλ‘œ κ΅¬λ™μ‹œν‚€κΈ° μœ„ν•΄ μž‘μ„±ν–ˆλ˜ 섀정듀을 μ†Œκ°œν•΄μ£Όμ„Έμš”.

μœ„μ—μ„œ ν•Έλ“€λŸ¬λ₯Ό λ³€κ²½ν•œκ²ƒμ— μΆ”κ°€μ μœΌλ‘œ setupTests.tsνŒŒμΌμ—μ„œλ„ μΆ”κ°€μ μœΌλ‘œ 섀정을 ν–ˆλŠ”λ°μš”.

import { setupServer } from 'msw/node';
import '@testing-library/jest-dom';

import { handlers } from './__mocks__/handlers';

/* msw */
export const server = setupServer(...handlers);

beforeAll(() => {
  server.listen();
  vi.useFakeTimers({ shouldAdvanceTime: true });
});

beforeEach(() => {
  expect.hasAssertions();
  vi.setSystemTime(new Date('2025-10-15'));
});

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

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

각 ν…ŒμŠ€νŠΈκ°€ λλ‚œ ν›„ 이전 ν…ŒμŠ€νŠΈμ˜ 영ν–₯을 μ™„μ „νžˆ μ œκ±°ν•˜λŠ” 것이라고 νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ setupTests.ts νŒŒμΌμ—μ„œ ν…ŒμŠ€νŠΈ 생λͺ…μ£ΌκΈ°λ₯Ό μ²΄κ³„μ μœΌλ‘œ κ΄€λ¦¬ν•˜λ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€. beforeAll λ‹¨κ³„μ—μ„œλŠ” MSW μ„œλ²„λ₯Ό μ‹œμž‘ν•˜κ³  FakeTimerλ₯Ό ν™œμ„±ν™”ν•©λ‹ˆλ‹€. FakeTimerλ₯Ό μ‚¬μš©ν•˜λŠ” μ΄μœ λŠ” μ‹œκ°„μ— 의쑴적인 ν…ŒμŠ€νŠΈλ“€μ΄ μ‹€ν–‰ ν™˜κ²½μ— 관계없이 μΌκ΄€λœ κ²°κ³Όλ₯Ό μ–»κΈ° μœ„ν•΄μ„œμž…λ‹ˆλ‹€. 특히 shouldAdvanceTime μ˜΅μ…˜μ„ true둜 μ„€μ •ν•˜μ—¬ 타이머가 μ‹€μ œλ‘œ μ§„ν–‰λ˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€. (ν•˜μœ„μ—μ„œλ„ 이 섀정에 λŒ€ν•œ 언급이 μžˆμŠ΅λ‹ˆλ‹€)

beforeEach λ‹¨κ³„μ—μ„œλŠ” 두 κ°€μ§€ μ€‘μš”ν•œ 섀정을 ν•©λ‹ˆλ‹€. 첫째둜 expect.hasAssertions()λ₯Ό ν˜ΈμΆœν•˜μ—¬ λͺ¨λ“  ν…ŒμŠ€νŠΈμ— μ΅œμ†Œ ν•˜λ‚˜ μ΄μƒμ˜ assertion이 μžˆλ„λ‘ κ°•μ œν•©λ‹ˆλ‹€. μ΄λŠ” 빈 ν…ŒμŠ€νŠΈλ‚˜ μ‹€μˆ˜λ‘œ assertion이 μ—†λŠ” ν…ŒμŠ€νŠΈλ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•¨μ΄μ§€λ§Œ, λΉ„λ™κΈ°μ μœΌλ‘œ 콜백이 μžˆλŠ” κ²½μš°μ— 이λ₯Ό μ‹€ν–‰ν•˜μ§€ μ•Šκ³  λ„˜μ–΄κ°ˆ 수 μžˆλŠ” 상황을 미리 μ°¨λ‹¨ν•˜λŠ” 역할도 ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Promiseκ°€ rejectλ˜κ±°λ‚˜ 콜백 ν•¨μˆ˜κ°€ ν˜ΈμΆœλ˜μ§€ μ•ŠλŠ” 경우, ν…ŒμŠ€νŠΈλŠ” μ„±κ³΅μœΌλ‘œ ν‘œμ‹œλ˜μ§€λ§Œ μ‹€μ œλ‘œλŠ” μš°λ¦¬κ°€ μ˜λ„ν•œ 검증 둜직이 μ „ν˜€ μ‹€ν–‰λ˜μ§€ μ•Šμ€ μƒνƒœκ°€ 될 수 μžˆμŠ΅λ‹ˆλ‹€. expect.hasAssertions()λ₯Ό μ‚¬μš©ν•˜λ©΄ 이런 경우 ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜λ„λ‘ ν•˜μ—¬ false positiveλ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (이 뢀뢄도 ν•˜μœ„μ— λ‚΄μš©μ •λ¦¬ ν•΄λ‘μ—ˆμŠ΅λ‹ˆλ‹€.) λ‘˜μ§Έλ‘œ vi.setSystemTime을 μ‚¬μš©ν•˜μ—¬ λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ λ™μΌν•œ μ‹œκ°„(2025-10-15)μ—μ„œ μ‹€ν–‰λ˜λ„λ‘ κ³ μ •ν•©λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ λ‚ μ§œλ‚˜ μ‹œκ°„μ— 의쑴적인 λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•  λ•Œ ν™˜κ²½μ— 관계없이 μΌκ΄€λœ κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€. κ°€μž₯ μ€‘μš”ν•œ 뢀뢄은 afterEach λ‹¨κ³„μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ server.resetHandlers()λ₯Ό ν˜ΈμΆœν•˜μ—¬ 이전 ν…ŒμŠ€νŠΈμ—μ„œ server.use()둜 μ„€μ •ν•œ λͺ¨λ“  MSW ν•Έλ“€λŸ¬λ₯Ό κΈ°λ³Έ μƒνƒœλ‘œ λ³΅μ›ν•˜λ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ vi.clearAllMocks()둜 λͺ¨λ“  mock ν•¨μˆ˜μ˜ 호좜 이λ ₯을 μ΄ˆκΈ°ν™”ν•˜κ³ , vi.clearAllTimers()둜 타이머 μƒνƒœλ₯Ό μ •λ¦¬ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ afterAll λ‹¨κ³„μ—μ„œλŠ” λͺ¨λ“  mock을 μ™„μ „νžˆ λ¦¬μ…‹ν•˜κ³  MSW μ„œλ²„λ₯Ό μ’…λ£Œν•œ ν›„ μ‹€μ œ νƒ€μ΄λ¨Έλ‘œ λ³΅μ›ν•©λ‹ˆλ‹€.

λ˜ν•œ μ‹€μ œ ν…ŒμŠ€νŠΈ νŒŒμΌμ—μ„œλŠ” 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ§ˆλ‹€ μ μ ˆν•œ ν•Έλ“€λŸ¬ μ„€μ • ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ—¬ 독립적인 ν™˜κ²½μ„ κ΅¬μ„±ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€. useEventOperations ν…ŒμŠ€νŠΈμ—μ„œλŠ” μƒˆλ‘œμš΄ 이벀트λ₯Ό μ €μž₯ν•˜λŠ” ν…ŒμŠ€νŠΈμ—μ„œ setupMockHandlerCreation([])을 ν˜ΈμΆœν•˜μ—¬ 빈 λ°°μ—΄λ‘œ μ‹œμž‘ν•˜λŠ” μ™„μ „νžˆ μƒˆλ‘œμš΄ ν™˜κ²½μ„ λ§Œλ“­λ‹ˆλ‹€. 이 ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰λ˜λŠ” λ™μ•ˆ λ‹€λ₯Έ μ–΄λ–€ ν…ŒμŠ€νŠΈμ˜ 데이터도 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μˆ˜μ •, μ‚­μ œλ“±μ˜ ν…ŒμŠ€νŠΈμ—μ„œλ„ λ§ˆμ°¬κ°€μ§€λ‘œ μƒˆλ‘œμš΄ ν™˜κ²½μ—μ„œ λ™μž‘ν•  수 μžˆλ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

enqueueSnackbar와 같은 μ™ΈλΆ€ 라이브러리 ν•¨μˆ˜λ“€λ„ 독립성을 보μž₯ν•΄μ•Ό ν•©λ‹ˆλ‹€. notistack 라이브러리λ₯Ό mockingν•  λ•Œ vi.fn()으둜 μƒμ„±ν•œ mock ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜κ³ , 각 ν…ŒμŠ€νŠΈ ν›„ vi.clearAllMocks()둜 호좜 이λ ₯을 μ΄ˆκΈ°ν™”ν–ˆμŠ΅λ‹ˆλ‹€.

심화 과제

  • App μ»΄ν¬λ„ŒνŠΈ μ μ ˆν•œ λ‹¨μœ„μ˜ μ»΄ν¬λ„ŒνŠΈ, ν›…, μœ ν‹Έ ν•¨μˆ˜λ‘œ λΆ„λ¦¬ν–ˆλŠ”κ°€?
  • ν•΄λ‹Ή λͺ¨λ“ˆλ“€μ— λŒ€ν•œ μ μ ˆν•œ ν…ŒμŠ€νŠΈλ₯Ό 5개 이상 μž‘μ„±ν–ˆλŠ”κ°€?

과제 μ…€ν”„νšŒκ³ 

ν…ŒμŠ€νŠΈμ½”λ“œλŠ” μ–Έμ œ 짜렀고 해도 λͺ…ν™•ν•˜κ²Œ 해닡이 μžˆλŠ” λ¬Έμ œκ°€ μ•„λ‹ˆλΌμ„œ μ–΄λ €μš΄ 것 κ°™μŠ΅λ‹ˆλ‹€. κ·Έλž˜λ„ μ΄λ²ˆμ£ΌλŠ” μˆ˜μ›”ν• μ€„ μ•Œμ•˜λŠ”λ°... λ°œν‘œλΌλŠ” 볡병이...! μš°ν•˜ν•˜... κ·Έλž˜λ„ μ–΄μ§Έμ €μ§Έ μ“°λŸ¬μ§€μ§„ μ•Šκ³  잘 λ§ˆλ¬΄λ¦¬ν–ˆμœΌλ‹ˆκΉŒ 된 κ±° κ² μ£ ...? image κ³Όμ œμ€‘μ— μ €μ—κ²Œ νž˜μ„ μ£Όμ‹ ...μ€€μΌλ‹˜μ˜ νŒ¬μ‹¬λ•λΆ„μ— μƒˆλ²½μ— 잠이 μ£½κ² λŠ”λ°λ„ μ΄κ²¨λƒˆμŠ΅λ‹ˆλ‹€...(μ§„μ§œμž„,,,λ‚˜μ—κ² 고양이같은...νŒ¬μ΄μžˆλ‹€)

기술적 μ„±μž₯

1. test() vs it()

Jestλ₯Ό ν†΅ν•΄μ„œ ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό μž‘μ„±ν• λ•Œ test와 it이 μ‘΄μž¬ν•¨μ„ μ•Œκ²Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 λ‘˜μ€ μ–΄λ–»κ²Œ λ‹€λ₯Έ κ²ƒμΌκΉŒμš”?

Jestμ—μ„œ test와 it은 κΈ°λŠ₯적으둜 μ™„μ „νžˆ λ™μΌν•œ ν•¨μˆ˜μž…λ‹ˆλ‹€. λ‘˜ λ‹€ κ°œλ³„ ν…ŒμŠ€νŠΈλ₯Ό μ •μ˜ν•˜λŠ” 데 μ‚¬μš©λ˜λ©°, 단지 alias 관계일 λΏμž…λ‹ˆλ‹€. 차이점은 μŠ€νƒ€μΌκ³Ό 가독성, 그리고 νŒ€μ˜ μ»¨λ²€μ…˜μ— μžˆμŠ΅λ‹ˆλ‹€.

// 이 λ‘˜μ€ μ •ν™•νžˆ 같은 ν•¨μˆ˜λ₯Ό κ°€λ¦¬ν‚΅λ‹ˆλ‹€
test('calculation works correctly', () => {
  expect(2 + 2).toBe(4);
});

it('calculation works correctly', () => {
  expect(2 + 2).toBe(4);
});

Jest 곡식 λ¬Έμ„œμ—μ„œ 확인할 수 μžˆλ“―μ΄, test λ©”μ„œλ“œλŠ” itμœΌλ‘œλ„ aliasedλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€μ μœΌλ‘œλŠ” μ™„μ „νžˆ λ™μΌν•œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ―€λ‘œ μ„±λŠ₯μ΄λ‚˜ κΈ°λŠ₯μƒμ˜ μ°¨μ΄λŠ” μ „ν˜€ μ—†μŠ΅λ‹ˆλ‹€.

λ˜ν•œ 두 ν•¨μˆ˜ λͺ¨λ‘ Jest의 λͺ¨λ“  κΈ°λŠ₯을 λ™μΌν•˜κ²Œ μ§€μ›ν•©λ‹ˆλ‹€.

// λ‘˜ λ‹€ timeout 지원
test('async operation', async () => { /* ... */ }, 10000);
it('async operation', async () => { /* ... */ }, 10000);

// λ‘˜ λ‹€ .only, .skip λ“±μ˜ modifier 지원
test.only('focused test', () => { /* ... */ });
it.only('focused test', () => { /* ... */ });

// λ‘˜ λ‹€ .each둜 데이터 주도 ν…ŒμŠ€νŠΈ 지원
test.each([[1, 2, 3]])('parameterized test', (a, b, expected) => {
  expect(a + b).toBe(expected);
});

it.each([[1, 2, 3]])('parameterized test', (a, b, expected) => {
  expect(a + b).toBe(expected);
});

What is the difference between 'it' and 'test' in Jest?에 λ”°λ₯΄λ©΄, test와 it의 차이λ₯Ό λ‹€μŒκ³Ό 같이 μ„€λͺ…ν•©λ‹ˆλ‹€.

describe('UserService', () => {
  test('μ‚¬μš©μž 생성이 μ˜¬λ°”λ₯΄κ²Œ λ™μž‘ν•œλ‹€', () => {
    const user = createUser('κΉ€μ² μˆ˜', 'kim@example.com');
    expect(user.name).toBe('κΉ€μ² μˆ˜');
  });

  test('잘λͺ»λœ μ΄λ©”μΌλ‘œ μ‚¬μš©μž 생성 μ‹œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€', () => {
    expect(() => createUser('κΉ€μ² μˆ˜', 'invalid-email')).toThrow();
  });
});

testλŠ” μš°μ„  ν•¨μˆ˜λͺ… μžμ²΄κ°€ "ν…ŒμŠ€νŠΈ"λΌλŠ” λͺ©μ μ„ λΆ„λͺ…νžˆ ν•˜μ—¬ 직접적이고 λͺ…ν™•ν•©λ‹ˆλ‹€.

λ°˜λ©΄μ— it은 BDD(Behavior Driven Development) μŠ€νƒ€μΌμ˜ μžμ—°μ–΄μ  ν‘œν˜„μž…λ‹ˆλ‹€.

describe('UserService', () => {
  it('should create user with valid data', () => {
    const user = createUser('κΉ€μ² μˆ˜', 'kim@example.com');
    expect(user.name).toBe('κΉ€μ² μˆ˜');
  });

  it('should throw error when email is invalid', () => {
    expect(() => createUser('κΉ€μ² μˆ˜', 'invalid-email')).toThrow();
  });
});

μœ„ μ½”λ“œμ²˜λŸΌ "it should..." νŒ¨ν„΄μœΌλ‘œ μžμ—°μ–΄μ— κ°€κΉŒμš΄ ν‘œν˜„μ΄κ³ , μš”κ΅¬μ‚¬ν•­μ„ ν…ŒμŠ€νŠΈλ‘œ 직접 λ²ˆμ—­ν•˜κΈ° μš©μ΄ν•©λ‹ˆλ‹€.

describe('Calculator', () => {
  it('should add two numbers correctly', () => {
    expect(add(2, 3)).toBe(5);
  });
  
  it('should handle negative numbers', () => {
    expect(add(-1, 1)).toBe(0);
  });
});

μ˜μ–΄μ—μ„œλŠ” it이 μžμ—°μŠ€λŸ½κ²Œ μ½νž™λ‹ˆλ‹€: "Calculator should add two numbers correctly"

describe('계산기', () => {
  // μ–΄μƒ‰ν•œ ν‘œν˜„
  it('두 숫자λ₯Ό μ˜¬λ°”λ₯΄κ²Œ 더해야 ν•œλ‹€', () => {
    expect(add(2, 3)).toBe(5);
  });
  
  // μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„  
  test('두 숫자λ₯Ό μ˜¬λ°”λ₯΄κ²Œ λ”ν•œλ‹€', () => {
    expect(add(2, 3)).toBe(5);
  });
});

ν•œκ΅­μ–΄λ‘œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” testκ°€ 더 μžμ—°μŠ€λŸ¬μš΄ κ²½μš°κ°€ λ§ŽμŠ΅λ‹ˆλ‹€.

// 일관성 μžˆλŠ” μ‚¬μš© (Good)
describe('AuthService', () => {
  test('둜그인 성곡 μ‹œ 토큰을 λ°˜ν™˜ν•œλ‹€', () => { /* ... */ });
  test('잘λͺ»λœ λΉ„λ°€λ²ˆν˜Έλ‘œ 둜그인 μ‹œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€', () => { /* ... */ });
  test('토큰 만료 μ‹œ μžλ™μœΌλ‘œ κ°±μ‹ ν•œλ‹€', () => { /* ... */ });
});

// 혼재된 μ‚¬μš© (Bad)
describe('AuthService', () => {
  test('둜그인 성곡 μ‹œ 토큰을 λ°˜ν™˜ν•œλ‹€', () => { /* ... */ });
  it('잘λͺ»λœ λΉ„λ°€λ²ˆν˜Έλ‘œ 둜그인 μ‹œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€', () => { /* ... */ });
  test('토큰 만료 μ‹œ μžλ™μœΌλ‘œ κ°±μ‹ ν•œλ‹€', () => { /* ... */ });
});

μ‹€μ œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” test, it 쀑 μ–΄λ–€ 것을 μ‚¬μš©ν•΄λ„ λ˜μ§€λ§Œ, νŒ€μ˜ 상황과 μ»¨λ²€μ…˜μ— 맞게 μ‚¬μš©ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€. μ•žμ„œ λ§ν–ˆλ“― test와 it의 μ°¨μ΄λŠ” 가독성에 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

κ²°κ΅­ μ€‘μš”ν•œ 것은 test냐 it이냐가 μ•„λ‹ˆλΌ, 의미있고 λͺ…ν™•ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λŠ” 것이라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

좜처

2. expect.hasAssertions()

Jest의 expect.hasAssertions()은 주둜 비동기 ν…ŒμŠ€νŠΈμ—μ„œ assertion이 μ‹€μ œλ‘œ μ‹€ν–‰λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€. μ—¬κΈ°μ„œ λ§ν•˜λŠ” assertion은 μš°λ¦¬κ°€ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•  λ•Œ μž‘μ„±ν•˜λŠ” expect().toBe(), expect().toEqual() 같은 검증 μ½”λ“œλ“€μ„ μ˜λ―Έν•©λ‹ˆλ‹€.

비동기 ν…ŒμŠ€νŠΈμ—μ„œ κ°€μž₯ μœ„ν—˜ν•œ 상황 쀑 ν•˜λ‚˜λŠ” ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν–ˆμ§€λ§Œ μ‹€μ œλ‘œλŠ” 아무것도 κ²€μ¦ν•˜μ§€ μ•Šμ€ κ²½μš°μž…λ‹ˆλ‹€.

function fetchUserData(userId, callback) {
  if (userId > 0) {
    api.getUser(userId).then(user => {
      callback(user); // 정상 μΌ€μ΄μŠ€λ§Œ callback 호좜
    });
    // .catch()κ°€ μ—†μ–΄μ„œ μ—λŸ¬ μ‹œ callback이 ν˜ΈμΆœλ˜μ§€ μ•ŠμŒ
  }
  // userId <= 0인 경우 callback이 μ „ν˜€ ν˜ΈμΆœλ˜μ§€ μ•ŠμŒ
}

// 이 ν…ŒμŠ€νŠΈλŠ” 잘λͺ» 톡과할 수 있음
test('fetchUserData handles invalid userId', () => {
  fetchUserData(-1, (user) => {
    expect(user).toBeNull(); // 이 assertion이 μ‹€ν–‰λ˜μ§€ μ•ŠμŒ
  });
  // JestλŠ” ν…ŒμŠ€νŠΈ ν•¨μˆ˜κ°€ λλ‚˜λ©΄ μ„±κ³΅μœΌλ‘œ κ°„μ£Ό
});

ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜λ©΄ 'fetchUserData handles invalid userId'인 κ²ƒμ²˜λŸΌ λ³΄μ΄μ§€λ§Œ μ‹€μ œλ‘œλŠ” assertion이 μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— ν•΄λ‹Ή ν…ŒμŠ€νŠΈλŠ” κ±°μ§“λœ ν…ŒμŠ€νŠΈκ°€ λ©λ‹ˆλ‹€.

κ·Έλ ‡κΈ° λ•Œλ¬Έμ— 이λ₯Ό expect.hasAssertions()λ₯Ό μΆ”κ°€ν•˜μ—¬ μ‹€ν–‰ν•˜λ©΄ λ‹€μŒκ³Ό 같이 λ™μž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

# hasAssertions()λ₯Ό μΆ”κ°€ν•œ ν›„ μ‹€ν–‰ κ²°κ³Ό
βœ— fetchUserData handles invalid userId (5ms)
  
  Error: Expected at least one assertion to be called but received zero assertion calls.

μ΄λ ‡κ²Œ μž‘μ„±ν•΄μ£Όλ©΄ ν…ŒμŠ€νŠΈκ°€ μ˜¬λ°”λ₯΄κ²Œ μ‹€νŒ¨ν•©λ‹ˆλ‹€. callback이 ν˜ΈμΆœλ˜μ§€ μ•Šμ•˜λ‹€λŠ” 것을 λͺ…ν™•νžˆ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

3. 미친 1μ‹œκ°„ λ°˜λ™μ•ˆμ˜ νŠΈλŸ¬λΈ” μŠˆνŒ… - Fake Timers와 비동기 μž‘μ—… κ°„μ˜ 타이밍 이슈

제 κ³Όμ œμƒμ˜ setupTest νŒŒμΌμ€ λ‹€μŒκ³Ό 같이 κ΅¬μ„±λ˜μ–΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

/* msw */
export const server = setupServer(...handlers);

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

beforeEach(() => {
  expect.hasAssertions();
  vi.setSystemTime(new Date('2025-10-15'));
});

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

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

그런데 과제λ₯Ό ν•˜λ‹€λ³΄λ‹ˆ μ΄μƒν•œ 점을 λ°œκ²¬ν•©λ‹ˆλ‹€.

image image image

이걸 1μ‹œκ°„ λ°˜λ™μ•ˆ μ‚½μ§ˆμ„ ν–ˆμ—ˆλŠ”λ°μš”. κ²°λ‘ μ μœΌλ‘œλŠ” https://github.com/testing-library/dom-testing-library/issues/1218 의 λ‚΄μš©μ„ 확인해보면 vi.useFakeTimers({ shouldAdvanceTime: true }); ν•œ μ€„λ§Œ μΆ”κ°€ν•΄μ£Όλ©΄ λ˜λŠ” λ¬Έμ œμ˜€μŠ΅λ‹ˆλ‹€. μ•„λ‹ˆ

이게 μ™œ κ·ΈλŸ°μ§€μ— λŒ€ν•΄μ„œλŠ” 일단 shouldAdvanceTime μ˜΅μ…˜μ— λŒ€ν•΄μ„œ 이해해야할 것 κ°™μŠ΅λ‹ˆλ‹€. https://github.com/vitest-dev/vitest/blob/main/docs/config/index.md#faketimersshouldadvancetime λ₯Ό 보면 shouldAdvanceTime μ˜΅μ…˜μ€

  • @sinonjs/fake-timersμ—κ²Œ μ‹€μ œ μ‹œμŠ€ν…œ μ‹œκ°„ 변화에 따라 μžλ™μœΌλ‘œ κ°€μ§œ μ‹œκ°„μ„ μ¦κ°€μ‹œν‚€λΌκ³  μ§€μ‹œν•©λ‹ˆλ‹€. (예: μ‹€μ œ μ‹œκ°„μ΄ 20ms λ³€ν•  λ•Œλ§ˆλ‹€ κ°€μ§œ μ‹œκ°„λ„ 20msμ”© 증가) 라고 λ‚˜μ™€μžˆλŠ”λ°μš”.

λ§Œμ•½ shouldAdvanceTimeκ°€ false(κΈ°λ³Έκ°’)인 κ²½μš°μ—λŠ” κ°œλ°œμžκ°€ λͺ…μ‹œμ μœΌλ‘œ vi.advanceTimersByTime()으둜 μˆ˜λ™ μ‘°μž‘ν•΄μ•Όλ§Œ μ‹œκ°„μ΄ 흐λ₯΄κ²Œ λ©λ‹ˆλ‹€. λ•Œλ¬Έμ— 비동기 라이브러리(제 κ³Όμ œμ—μ„œμ˜ findByText)와 ν˜Έν™˜μ„± λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ°˜λŒ€λ‘œ shouldAdvanceTimeκ°€ true인 κ²½μš°μ—λŠ” μœ„μ—μ„œ μ–ΈκΈ‰λœ κ²ƒμ²˜λŸΌ μ‹€μ œ μ‹œκ°„μ΄ 흐λ₯΄λ©΄ κ°€μ§œ μ‹œκ°„λ„ λ”°λΌμ„œ 흐λ₯΄κΈ° λ•Œλ¬Έμ— 비동기 λΌμ΄λΈŒλŸ¬λ¦¬λ“€κ³Ό 잘 μž‘λ™ν•©λ‹ˆλ‹€.

λ‹€μ‹œ 제 과제둜 λŒμ•„κ°€μ„œ

vi.useFakeTimers(); // shouldAdvanceTime: false (κΈ°λ³Έκ°’)

await act(async () => {
  setup(<App />);
  vi.advanceTimersByTime(100); // ← μ΄λ•ŒκΉŒμ§€λ§Œ μ‹œκ°„μ΄ 흐름
});

// ν•˜μ§€λ§Œ 이 μ‹œμ λΆ€ν„° μ‹œκ°„μ΄ μ™„μ „νžˆ 멈좀
await screen.findByText('일정 λ‘œλ”© μ™„λ£Œ!'); // ← νƒ€μž„μ•„μ›ƒ!

shouldAdvanceTimeκ°€ falseμ΄λ‹ˆ λͺ…μ‹œμ μœΌλ‘œ μ‹œκ°„μ„ λŒλ €μ£Όμ§€ μ•ŠμœΌλ©΄ μ‹œκ°„μ΄ 흐λ₯΄μ§€ μ•Šκ³ 

// findByText λ‚΄λΆ€ ꡬ쑰 (λ‹¨μˆœν™”)
const findByText = (text) => {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject(new Error('νƒ€μž„μ•„μ›ƒ!')); // ← 이 타이머가 μ‹€ν–‰λ˜μ§€ μ•ŠμŒ
    }, 1000);
    
    const poll = () => {
      const element = queryByText(text);
      if (element) {
        resolve(element);
      } else {
        setTimeout(poll, 50); // ← 이 타이머도 μ‹€ν–‰λ˜μ§€ μ•ŠμŒ
      }
    };
    poll();
  });
};

findByTextκ°€ λ‚΄λΆ€μ μœΌλ‘œ 타이머λ₯Ό μ‹€ν–‰ν•˜λŠ”λ°, μ΄λ•Œ 타이머가 멈좰있게 λ˜μ–΄ νƒ€μž„μ•„μ›ƒμ΄ λ°œμƒν•œ...κ²ƒμ΄μ—ˆμŠ΅λ‹ˆλ‹€.

ν•œμ‹œκ°„ λ°˜μ΄λ‚˜ νˆ¬μžν–ˆμœΌλ‹ˆ λ‹€μŒλ²ˆμ—” 잘 κΈ°μ–΅ν•˜κ² μ§€ ν•˜κ³ ... λ†“μ•„μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

μ½”λ“œ ν’ˆμ§ˆ

1. λΆˆν•„μš”ν•œ ν…ŒμŠ€νŠΈ

λΆˆν•„μš”ν•œ ν…ŒμŠ€νŠΈλΌκ³  μ—¬κ²¨μ§€λŠ” 뢀뢄에 λŒ€ν•΄μ„œ 상단에 주석을 λ‹¬μ•„λ‘μ—ˆμŠ΅λ‹ˆλ‹€.

// getDaysInMonth에 λŒ€ν•œ ν…ŒμŠ€νŠΈ 쀑
  // NOTE: 이 ν…ŒμŠ€νŠΈλŠ” λΆˆν•„μš”ν•œ ν…ŒμŠ€νŠΈλΌκ³  νŒλ‹¨ν•˜μ˜€μœΌλ‚˜ μ΅œλŒ€ν•œ ν˜„μž¬ κΈ°λŠ₯에 μœ μ‚¬ν•˜κ²Œ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  // ν…ŒμŠ€νŠΈλ₯Ό λΆˆν•„μš”ν•˜λ‹€κ³  νŒλ‹¨ν•œ μ΄μœ λŠ”, getDaysInMonth ν•¨μˆ˜κ°€ JavaScript λ‚΄μž₯ κΈ°λŠ₯ ν…ŒμŠ€νŠΈμ— μ§€λ‚˜μΉ˜μ§€ μ•ŠλŠ”λ‹€κ³  νŒλ‹¨ν–ˆκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
  describe('잘λͺ»λœ μ›” μž…λ ₯ 처리', () => {
    test('13μ›” μž…λ ₯ μ‹œ 이전 달(12μ›”)의 일수인 31일을 λ°˜ν™˜ν•œλ‹€', () => {
      expect(getDaysInMonth(2025, 13)).toBe(31);
    });

    test('0μ›” μž…λ ₯ μ‹œ 이전 달(12μ›”)의 일수인 31일을 λ°˜ν™˜ν•œλ‹€', () => {
      expect(getDaysInMonth(2025, 0)).toBe(31);
    });

    test('-1μ›” μž…λ ₯ μ‹œ 이전 달(11μ›”)의 일수인 30일을 λ°˜ν™˜ν•œλ‹€', () => {
      expect(getDaysInMonth(2025, -1)).toBe(30);
    });
  });
  
  // getTimeErrorMessage에 λŒ€ν•œ ν…ŒμŠ€νŠΈ 쀑
    describe('μž…λ ₯값이 λΉ„μ–΄μžˆλŠ” 경우', () => {
    test('μ‹œμž‘ μ‹œκ°„μ΄ λΉ„μ–΄μžˆμ„ λ•Œ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠλŠ”λ‹€', () => {
      const result = getTimeErrorMessage('', '15:00');
      expect(result.startTimeError).toBeNull();
      expect(result.endTimeError).toBeNull();
    });

    test('μ’…λ£Œ μ‹œκ°„μ΄ λΉ„μ–΄μžˆμ„ λ•Œ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠλŠ”λ‹€', () => {
      const result = getTimeErrorMessage('14:00', '');
      expect(result.startTimeError).toBeNull();
      expect(result.endTimeError).toBeNull();
    });

    // NOTE: ν•΄λ‹Ή ν…ŒμŠ€νŠΈμ˜ 경우 μƒμœ„μ˜ 두 ν…ŒμŠ€νŠΈμ—μ„œ 검증이 κ°€λŠ₯ν•˜λ―€λ‘œ λΆˆν•„μš”ν•œ ν…ŒμŠ€νŠΈλ‘œ μƒκ°λ©λ‹ˆλ‹€.
    test('μ‹œμž‘ μ‹œκ°„κ³Ό μ’…λ£Œ μ‹œκ°„μ΄ λͺ¨λ‘ λΉ„μ–΄μžˆμ„ λ•Œ null을 λ°˜ν™˜ν•œλ‹€', () => {
      const result = getTimeErrorMessage('', '');
      expect(result.startTimeError).toBeNull();
      expect(result.endTimeError).toBeNull();
    });
  });

μ œκ°€ νŒλ‹¨ν•œ 'λΆˆν•„μš”ν•¨'의 기쀀은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. κ΅¬ν˜„μ²΄κ°€ 이미 λΈŒλΌμš°μ € api둜 μΆ©λΆ„νžˆ νŒŒμ•… κ°€λŠ₯ν•œ ν…ŒμŠ€νŠΈλ‹€.
  2. ν•˜μœ„ ν…ŒμŠ€νŠΈμ—μ„œ μΆ©λΆ„νžˆ 검증 κ°€λŠ₯ν•œ ν…ŒμŠ€νŠΈλ‹€.
  3. μ•žμ„  ν…ŒμŠ€νŠΈμ—μ„œ μΆ©λΆ„νžˆ 검증 λ˜μ—ˆλ‹€.

λ•Œλ¬Έμ— μƒκΈ°ν•œ ν…ŒμŠ€νŠΈλ“€μ€ λΆˆν•„μš”ν•˜λ‹€κ³  νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.

2. ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λŠ” κ·œμΉ™

μ œκ°€ μ΄λ²ˆμ— ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•¨μ— μžˆμ–΄μ„œ 슀슀둜 κ·œμΉ™μ„ μ„Έμ›Œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ˜€λŠ”λ°, κ·Έ κ·œμΉ™μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. Given When Then λ°©μ‹μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•  것
  2. describeκ°€ ꡬ체적일것
  3. λͺ¨λ“  κ΅¬ν˜„μ„ μ»€λ²„ν•˜μ§€ μ•Šμ•„λ„ 되고, ν•„μš”ν•œ 뢀뢄에 λŒ€ν•΄μ„œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν• κ²ƒ
  4. λ¬΄μ˜λ―Έν•œν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ§€ μ•Šμ„κ²ƒ
  5. μ •μƒλ²”μœ„μ— λŒ€ν•΄μ„œλŠ” ν…ŒμŠ€νŠΈκ°€ ν•˜λ‚˜λ§Œ 있고, 경계값이 μžˆλ‹€λ©΄ 그에 λŒ€ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν• κ²ƒ. κ³Όλ„ν•œ μ˜€λ²„μ—”μ§€λ‹ˆμ–΄λ§ κΈˆμ§€ μ΄μ „μ—λŠ” ν…ŒμŠ€νŠΈ 컀버리지가 높은 ν…ŒμŠ€νŠΈ === 쒋은 ν…ŒμŠ€νŠΈλΌλŠ” 생각을 ν–ˆμ—ˆλŠ”λ° μ΄λ²ˆμ— ν…ŒμŠ€νŠΈλ₯Ό κ΅¬ν˜„ν•˜λ©΄μ„œλŠ” μ–΄λ–€ 것이 쒋은 ν…ŒμŠ€νŠΈμΌκΉŒλ₯Ό 더 μƒκ°ν•˜λ©΄μ„œ κ΅¬ν˜„ν–ˆλ˜ 것 κ°™μŠ΅λ‹ˆλ‹€.(λ¬Όλ‘  아직도 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€)

3. QnAλ•Œ λ“€μ—ˆλ˜ νžŒνŠΈμ— κΈ°λ°˜ν•΄μ„œ ν…ŒμŠ€νŠΈ κ΅¬ν˜„ν•˜κΈ°

이뢀뢄은 사싀 μ œκ°€ κ°ˆν”Όλ₯Ό 잘 μž‘μ€κ²Œ λ§žλ‚˜ 싢은 생각이 λ“œλŠ”λ°μš”. 일단 μ œκ°€ μ§ˆλ¬Έλ“œλ Έλ˜ 질문 쀑

μ–΄λ–€ μœ λ‹› ν…ŒμŠ€νŠΈμ˜ event에 λŒ€ν•΄μ„œ λͺ©λ°μ΄ν„°λ₯Ό μœ„μΉ˜λ₯Ό κ°€κΉκ²Œ λ‘¬μ•Όν• κΉŒμš”? μ•„λ‹ˆλ©΄ 멀리 λ‘λŠ”κ²Œ μ’‹μ„κΉŒμš”? κ°€κΉŒμ΄ λ‘”λ‹€λŠ” μ˜λ―ΈλŠ” ν…ŒμŠ€νŠΈ 파일 ν˜Ήμ€ 각 λͺ¨λ“ˆ 내뢀에 λͺ©λ°μ΄ν„°λ₯Ό λ‘λŠ”κ²ƒ, 멀리 λ‘”λ‹€λŠ” 것은 λ”°λ‘œ fixtureλ₯Ό 두어 κ³΅ν†΅ν™”λœ 데이터λ₯Ό μ‚¬μš©ν•¨μ„ μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. κ°€κΉκ²Œ 두면 μ˜λ„κ°€ λΆ„λͺ…ν•˜λ‹€λŠ” 이점이 μžˆμ„ 것 κ°™μ€λ°μš”β€¦κ·ΈμΉ˜λ§Œ 좔후에 μΈν„°νŽ˜μ΄μŠ€κ°€ λ³€κ²½λ˜λ©΄ λͺ¨λ“  κ³³μ—μ„œ λ³€κ²½ν•΄μ£Όμ–΄μ•Ό ν•΄μ„œ κ΄€λ¦¬ν•˜κΈ°κ°€ μ–΄λ €μšΈ 수 μžˆμ„κ±°κ°™μŠ΅λ‹ˆλ‹€. μ–΄λ–€κ²Œ 더 λ‚˜μ€ λ°©λ²•μΌκΉŒμš”?

λΌλŠ” μ§ˆλ¬Έμ„ ν†΅ν•΄μ„œ, 일반적인 μΌ€μ΄μŠ€λ“€μ— λŒ€ν•΄μ„œλŠ” κ΄€ν†΅ν•˜λŠ” fixtureλ₯Ό 두고 νŠΉμˆ˜ν•œ μΌ€μ΄μŠ€μ— λŒ€ν•΄μ„œλ§Œ νŠΉμˆ˜ν•œ mockEventsλ₯Ό 두도둝 λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.

κ°€μž₯ μ‹ κ²½μΌλ˜ 뢀뢄은 'ν…ŒμŠ€νŠΈλŠ” ν†΅κ³Όν•˜μ§€λ§Œ μ‹€μ œλ‘œλŠ” ν†΅κ³Όν•˜μ§€ μ•Šμ€ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό 찾아라'λΌλŠ” μ§„μ§œ 수수께끼 같은 λ§μ”€μ΄μ—ˆλŠ”λ°μš”. κ·Έλ₯Ό 계속 μ‹ κ²½μ“°λ©΄μ„œ 짜렀고 ν–ˆλŠ”λ° μ–΄λ–€ λΆ€λΆ„μ—μ„œ κ·Έ 뢀뢄이 ν•„μš”ν•œ 것인지 νŒŒμ•…μ΄ μ–΄λ €μ› μŠ΅λ‹ˆλ‹€.

그런데 setupTests.tsλ₯Ό κ΅¬ν˜„ν•˜λ‹€κ°€ hasAsstionsλΌλŠ” ν•¨μˆ˜μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄κ²Œ λ˜μ—ˆμ„λ•Œ '주둜 비동기 ν…ŒμŠ€νŠΈμ—μ„œ assertion이 μ‹€μ œλ‘œ μ‹€ν–‰λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•œλ‹€' λΌλŠ” 뢀뢄이 μžˆμ—ˆκ³  QnAμ—μ„œλ„ μ½”μΉ˜λ‹˜κ»˜μ„œ μ„€λͺ…ν•œ 뢀뢄이 데이터λ₯Ό λΆˆλŸ¬μ˜¬λ•Œ λΉ„λ™κΈ°μ μœΌλ‘œ 데이터λ₯Ό 뢈러였게 λ˜λŠ”λ° 이에 λŒ€ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄μ•Όν•œλ‹€? λΌλŠ” λŠ¬μ•™μŠ€κ°€ 있던 κ²ƒμœΌλ‘œ κΈ°μ–΅ν•˜λŠ”λ°μš”. 이번 ν…ŒμŠ€νŠΈμ˜ κ²½μš°μ— 데이터λ₯Ό λ‘œλ”©ν•˜κ³ λ‚˜μ„œ '일정 λ‘œλ”© μ™„λ£Œ!'λΌλŠ” μŠ€λ‚΅λ°”κ°€ λ³΄μ—¬μ§€κ²Œ λ˜μ–΄ 이λ₯Ό ν†΅ν•©ν…ŒμŠ€νŠΈμ—μ„œ κ²€μ¦ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

  test('월별 뷰에 일정이 μ—†μœΌλ©΄, 일정이 ν‘œμ‹œλ˜μ§€ μ•Šμ•„μ•Ό ν•œλ‹€.', async () => {
    // Given: ν•΄λ‹Ή 월에 일정이 μ—†λŠ” μƒνƒœλ‘œ μ•± μ‹œμž‘
    vi.setSystemTime(new Date('2019-01-01'));
    setup(<App />);
    await screen.findByText('일정 λ‘œλ”© μ™„λ£Œ!');

    // When: 월별 λ·°λ₯Ό 확인 (κΈ°λ³Έκ°’)
    // Then: 검색 κ²°κ³Όκ°€ μ—†λ‹€λŠ” λ©”μ‹œμ§€κ°€ ν‘œμ‹œλ˜μ–΄μ•Ό 함
    const eventList = within(screen.getByTestId('event-list'));
    expect(eventList.getByText('검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.')).toBeInTheDocument();
  });

일정이 μ—†λŠ” κ²½μš°μ—λŠ” 일정이 λ§κ·ΈλŒ€λ‘œ ν‘œμ‹œλ˜μ§€ μ•ŠμœΌλ―€λ‘œ 일정이 μ „μ²΄μ μœΌλ‘œ λ‘œλ”©λ˜μ—ˆλŠ”μ§€μ— λŒ€ν•΄ νŒλ‹¨ν•˜κΈ°κ°€ μ–΄λ €μš°λ―€λ‘œ 이λ₯Ό '일정 λ‘œλ”© μ™„λ£Œ!' ν† μŠ€νŠΈκ°€ λ°œμƒν•˜λŠ”μ§€λ₯Ό νŒŒμ•…ν•˜λ„λ‘ κ΅¬ν˜„ν–ˆκ³  κ·Έλ₯Ό ν†΅ν•΄μ„œ μ’€ 더 μ•ˆμ •μ μΈ ν…ŒμŠ€νŠΈ κ΅¬ν˜„μ΄ κ°€λŠ₯ν–ˆλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

κ°‘μžκΈ° μž‘μ„±ν•˜λ©΄μ„œ λ“  ꢁ금증인데, κ·Έλ ‡λ‹€λ©΄ λ§Œμ•½ λ‘œλ”©ν›„μ— μ €λŸ° ν† μŠ€νŠΈ λ©”μ‹œμ§€κ°€ μ—†λŠ” κ²½μš°μ—λŠ” μ–΄λ–»κ²Œ λΉ„λ™κΈ°μ μœΌλ‘œ 데이터가 λΆˆλŸ¬μ™€μ‘ŒμŒμ„ μ•Œ 수 μžˆμ„κΉŒμš”? μž‘μ„±ν•˜λ©΄μ„œ κΆκΈˆν•΄μ§‘λ‹ˆλ‹€.

ν•™μŠ΅ 효과 뢄석

μ‚¬μ‹€μ€μš”...

과제λ₯Ό ν•˜λ‹€κ°€ λͺ¨λ‹¬ κ΄€λ¦¬ν•˜λŠ” 뢀뢄에 λŒ€ν•΄μ„œ useOverlayλΌλŠ” 훅을 λ§Œλ“€μ–΄λ³ΌκΉŒ μƒκ°ν–ˆλŠ”λ°, κ·Έλ ‡κ²Œ 되면 ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό μ§œμ•Όν• κ±°κ°™λ‹€λŠ” 압박감이 λ“€μ–΄ ν† μŠ€μ˜ overlay-kitλ₯Ό...μ„€μΉ˜ν–ˆμŠ΅λ‹ˆλ‹€...γ…Žγ…Ž 이뢀뢄을 Pr에닀가 'μ €λŠ” ν…ŒμŠ€νŠΈλ₯Ό μ „λž΅μ μœΌλ‘œ λΌμ΄λΈŒλŸ¬λ¦¬μ— μœ„μž„ν–ˆμŠ΅λ‹ˆλ‹€'ν•˜κ³  μ‹Άμ—ˆλŠ”λ° 사싀 μ’€ 더 ν™•μ‹€ν•œ 보증(?)이 있으면 μ’‹κ² λ‹€κ³  μƒκ°ν•˜λ˜ μ°°λ‚˜μ˜€μŠ΅λ‹ˆλ‹€.

μ˜€ν”„μ½”μΉ˜λ‹˜μ΄ 개인적으둜 μ§ˆλ¬Έλ“œλ¦° λ‚΄μš©μ„ λ¬Έμ˜κΈ€μ— μ˜¬λ¦¬μ…¨λŠ”λ°

이 뢀뢄에 λŒ€ν•œ 검증은 html, reactκ°€ 잘 κ΅¬ν˜„μ΄ λ˜μ–΄μžˆμœΌλ©΄ 잘 될거닀! μ •λ„λ‘œ λ³Ό μˆ˜λ„ μžˆλŠ” λΆ€λΆ„μΌμˆ˜λ„

라고 ν•˜μ…”μ„œ μ–΄? 이거 λ‚˜ν•œν…Œ ν•„μš”ν•œ λ¬Έμž₯인데 μ‹Άμ–΄μ„œ μ΄λ•Œλ₯Ό λ†“μΉ˜λ©΄ μ•ˆλœλ‹€κ³  생각해 λ°”λ‘œ μ§ˆλ¬Έμ„ λ“œλ ΈμŠ΅λ‹ˆλ‹€. image

μ›λž˜ ν—ˆλ½λ³΄λ‹€ μš©μ„œκ°€ λΉ λ₯΄κ³ , overlay-kit 을 써도 λ˜λƒ 여쭀보면 직접 κ΅¬ν˜„ν•˜κ³  ν…ŒμŠ€νŠΈ μž‘μ„±ν•˜λΌλŠ” 말씀을 ν•˜μ‹€ 것 κ°™μ•„μ„œ γ…Žγ…Ž μ•ˆμ—¬μ­€λ³΄κ³  μ§„ν–‰ν•˜λ €κ³  ν–ˆλŠ”λ° κΈ°νšŒλŠ” μ—­μ‹œ μ°Ύμ•„μ˜€λ”λΌκ³ μš”. image image 일단 λ„Ήμ΄λΌλŠ” κΈ€μžλ₯Ό 보자마자 'μ΄λ•Œλ‹€' μ‹Άμ—ˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ κΈ€μžλŠ” λˆˆμ— 보이지도 μ•Šκ³  λ‚΄κ°€ 라이브러리λ₯Ό μ‚¬μš©ν•œ 것에 λŒ€ν•΄μ„œ ν—ˆλ½μ •λ„κ°€ μ•„λ‹ˆλΌ νƒ€λ‹Ήν•œ 이유λ₯Ό λ§Œλ“€μ–΄ 주심에 λ„ˆλ¬΄ κ°μ‚¬ν–ˆμŠ΅λ‹ˆλ‹€ γ…Žγ…Ž image μ œκ°€ λ¬Έμ˜κΈ€μ— μ§ˆλ¬Έν•œ λ‚΄μš© 쀑 κ°€μž₯ 재밌던 μ§ˆλ¬Έμ΄μ—ˆλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. γ…Žγ…Ž (μ•”νŠΌ μ „λž΅μ μ΄μž–μ•„~)

μ•”νŠΌ λΉ„μŠ·ν•˜κ²Œ, 사싀 μ‹€λ¬΄μ μœΌλ‘œ ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό 계속 ν•΄μ„œ κ΄€λ¦¬ν•˜λŠ” 쑰직이 μ•„λ‹ˆλΌλ©΄ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” e2eκ°€ μ•„λ‹ˆλ”λΌλ„ κ°’λΉ„μ‹Ό μ½”λ“œλΌκ³  생각이 λ“œλŠ”λ°μš”. 이런 뢀뢄을 λΌμ΄λΈŒλŸ¬λ¦¬μ— μœ„μž„ν•΄μ„œ κ²€μ¦ν† λ‘ν•˜λŠ” 방식이 μœ μ˜λ―Έν•˜λ‹€λŠ” 것을 μ•Œκ²Œλ˜μ—ˆλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

또 μ΄λ²ˆμ— μ€€μΌλ‹˜μ΄ μ•Œλ €μ£Όμ‹  https://www.npmjs.com/package/fast-check μš” 라이브러리λ₯Ό 이번 κ³Όμ œμ— 써보고 μ‹Άμ—ˆλŠ”λ° κ·ΈλŸ¬μ§€ λͺ»ν•΄μ„œ μ’€ 아쉬움이 λ‚¨μŠ΅λ‹ˆλ‹€. 이뢀뢄을 랜덀적으둜 λ°œμƒν•˜λŠ” μˆ«μžμ— λŒ€ν•œ κ²€μ¦μš©λ„λ‘œ μ‚¬μš©ν•  수 μžˆμ—ˆμ„ 것 같은데...μ•„λ¬΄νŠΌ λ‹€μŒμ— 싀무에 μ μš©ν•΄ λ³Ό 수 μžˆλ‹€λ©΄ μ¨λ΄„μ§ν•œ μˆ˜ν™•μ΄λΌκ³  μƒκ°ν•©λ‹ˆλ‹€.

과제 ν”Όλ“œλ°±

μ˜€ν”„μ½”μΉ˜λ‹˜κ»˜μ„œ μˆ¨κ²¨λ†“μœΌμ…¨λ‹€λŠ” μ΄μŠ€ν„°μ—κ·Έ(?)듀이 λ„ˆλ¬΄λ„ˆλ¬΄ κΆκΈˆν•©λ‹ˆλ‹€...μ œκ°€ μ˜λ„λ₯Ό μ œλŒ€λ‘œ νŒŒμ•…ν•˜λŠ”κ²Œ λ§žλŠ”κ²ƒμΈμ§€..λ„ˆλ¬΄λ„ˆλ¬΄λ„ˆλ¬΄ κΆκΈˆν•©λ‹ˆλ‹€... 이번주 λ°œμ œλ•Œ μ•Œλ €μ£Όμ…¨μœΌλ©΄ μ’‹κ² μ–΄μš” ... πŸ₯Ή

리뷰 λ°›κ³  싢은 λ‚΄μš©

  1. useEventFormμ΄λΌλŠ” μ• κ°€ λ„ˆλ¬΄ μ±…μž„μ΄ κ±°λŒ€ν•œκ²ƒ κ°™μ•„μ„œ λ‚΄λΆ€μ μœΌλ‘œ 역할을 λΆ„λ¦¬ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 그쀑에 useTimeValidationμ΄λΌλŠ” 훅을 λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. 이 훅은 λ‚΄λΆ€μ μœΌλ‘œ getTimeErrorMessageλ₯Ό μ“°λŠ”λ° ν•΄λ‹Ή μœ ν‹Έμ€ 이미 μœ λ‹›ν…ŒμŠ€νŠΈκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€. 일단 μ €λŠ” 이 getTimeErrorMessage에 λŒ€ν•œ 검증을 useTimeValidationμ—μ„œ ν•˜λŠ”κ²Œ μ˜λ―Έκ°€ μ—†λ‹€κ³  μƒκ°ν•˜λŠ”λ°μš”.

κ·Έλ ‡λ‹€λ©΄ useTimeValidationμ—μ„œ ν• μˆ˜μžˆλŠ” ν…ŒμŠ€νŠΈλŠ” κ΅¬ν˜„μ— λŒ€ν•œ λΆ€λΆ„λ§Œ 보면 μƒνƒœ μ—…λ°μ΄νŠΈ, μΈν„°νŽ˜μ΄μŠ€κ΄€λ ¨, ν•Έλ“€λŸ¬λ₯Ό μƒμ„±ν•˜λŠ” 뢀뢄에 λŒ€ν•œ 것 정도가 될거같은데 이런 뢀뢄에 λŒ€ν•œ 검증이 μ˜λ―Έκ°€ μžˆλŠ” κ²ƒμΌκΉŒμš”?

μ •λ¦¬ν•˜μžλ©΄ μœ ν‹Έν•¨μˆ˜μ— λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ κ΅¬ν˜„λ˜μ–΄μžˆκ³ , 이λ₯Ό λ‹¨μˆœνžˆ μ΄μš©ν•΄μ„œ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 훅이 μžˆλ‹€κ³  ν•˜λ©΄ 이 훅에 λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ μœ μ˜λ―Έν•˜λ‹€κ³  μƒκ°ν•˜μ‹œλ‚˜μš”? '였λ₯˜ μƒνƒœμ—μ„œ 정상적인 κ°’μœΌλ‘œ λ³€κ²½ν•˜λ©΄ 였λ₯˜κ°€ μ΄ˆκΈ°ν™”λ˜μ–΄μ•Ό ν•œλ‹€' 와 같이 μƒνƒœ μ—…λ°μ΄νŠΈ μ •λ„λŠ” 검증할 수 μžˆκ² λ‹€λŠ” 생각이 λ“œλŠ”λ°, μ–΄λ””κΉŒμ§€ 검증을 ν•΄μ•Όν•˜λŠ”μ§€ μ’€ λͺ¨ν˜Έν•œ 것 κ°™μŠ΅λ‹ˆλ‹€.

  1. λ‹¨μˆœνžˆ presentation μ»΄ν¬λ„ŒνŠΈλ‘œ λ·°μž‰μ—­ν• μ„ ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈκ°€ μžˆμ„λ•Œ props둜 ν•Έλ“€λŸ¬λ“€μ΄ λ„˜μ–΄μ˜¨λ‹€λŠ” 이유둜 μ–΄λ–€ μ΄λ²€νŠΈκ°€ λ°œμƒν–‡μ„λ•Œ 이벀트 ν•Έλ“€λŸ¬λ₯Ό λͺ¨ν‚Ήν•΄μ„œ ν…ŒμŠ€νŠΈ ν•˜λŠ”κ²Œ μ˜λ―Έκ°€ μžˆμ„κΉŒμš”?

  2. 데이터가 μ „λΆ€ λΉ„μ–΄μ ΈμžˆλŠ” 상황인 κ²½μš°μ— λ‘œλ”©ν›„μ— '일정 λ‘œλ”© μ™„λ£Œ!'와 같은 ν† μŠ€νŠΈ λ©”μ‹œμ§€κ°€ μ—†λ‹€λ©΄ μ–΄λ–»κ²Œ 데이터가 λΆˆλŸ¬μ™€μ‘ŒμŒμ„ μ•Œμ•„ 차릴 수 μžˆμ„κΉŒμš”?

과제 ν”Όλ“œλ°±

μ•ˆλ…•ν•˜μ„Έμš” μ˜μ„œλ‹˜! 7μ£Όμ°¨ 과제 잘 μ§„ν–‰ν•΄μ£Όμ…¨λ„€μš” γ…Žγ…Ž μ—¬λŸ¬κ°€μ§€ 일이 κ²Ήμ³μ„œ 항상 κ·Ήν•œμ˜ μ±Œλ¦°μ§€(?)λ₯Ό ν•˜κ³  μžˆλŠ”λ° κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  λšλ”±λšλ”± 잘 ν•΄λ‚΄λŠ” λͺ¨μŠ΅μ΄ λ©‹μžˆμœΌλ©΄μ„œλ„ κ±±μ •λ˜λ„€μš” γ…‹γ…‹ λ„ˆλ¬΄ λ¬΄λ¦¬ν•˜μ§„ μ•Šμ•˜μœΌλ©΄ μ’‹κ² μ”λ‹ˆλ‹€..

test() vs it()

사싀 이에 λŒ€ν•΄ 깊게 고민해본적이 μ—†μ—ˆλŠ”λ°, μž‘μ„±ν•΄μ£Όμ‹  λ‚΄μš©μ„ 보고 "μ•„ν•˜! κ·Έλ ‡κ΅¬λ‚˜!" λΌλŠ” 생각을 ν–ˆμ–΄μš”! κ΅Ώκ΅Ώ

expect.hasAssertions()

이것도 처음 λ³΄λŠ”λ° 덕뢄에 μƒˆλ‘œμš΄ APIλ₯Ό μ•Œμ•„κ°‘λ‹ˆλ‹€ γ…‹γ…‹ κ°μ‚¬ν•΄μš”

미친 1μ‹œκ°„ λ°˜λ™μ•ˆμ˜ νŠΈλŸ¬λΈ” μŠˆνŒ… - Fake Timers와 비동기 μž‘μ—… κ°„μ˜ 타이밍 이슈

γ…‹γ…‹γ…‹ 저도 2κΈ° λ•Œ 과제 μ†”λ£¨μ…˜ λ§Œλ“€λ©΄μ„œ λ™μΌν•œ 문제λ₯Ό κ²ͺμ—ˆλŠ”λ°μš”, 1μ‹œκ°„λ§Œμ— ν•΄κ²°ν•˜μ…¨λ‹€λ‹ˆ!!! λ©‹μžˆλ„€μš”..

κ°‘μžκΈ° μž‘μ„±ν•˜λ©΄μ„œ λ“  ꢁ금증인데, κ·Έλ ‡λ‹€λ©΄ λ§Œμ•½ λ‘œλ”©ν›„μ— μ €λŸ° ν† μŠ€νŠΈ λ©”μ‹œμ§€κ°€ μ—†λŠ” κ²½μš°μ—λŠ” μ–΄λ–»κ²Œ λΉ„λ™κΈ°μ μœΌλ‘œ 데이터가 λΆˆλŸ¬μ™€μ‘ŒμŒμ„ μ•Œ 수 μžˆμ„κΉŒμš”? μž‘μ„±ν•˜λ©΄μ„œ κΆκΈˆν•΄μ§‘λ‹ˆλ‹€.

이건 뭐.. λ‚΄λΆ€ 둜직이 μ–΄λ–»κ²Œ κ΅¬ν˜„λ˜μ—ˆλŠ”κ°€μ— 따라 λ‹€λ₯΄λ‹€κ³  μƒκ°ν•΄μš” γ…Žγ…Ž νŠΉμ € ν•¨μˆ˜μ˜ 호좜둜 확인해볼 μˆ˜λ„ 있고, ν˜Ήμ€ tanstack-queryλ₯Ό μ‚¬μš©ν•œλ‹€κ³  ν–ˆμ„ λ•Œ κ°•μ œλ‘œ onSuccess ν˜Ήμ€ onError 같은 이벀트λ₯Ό λ°–μ—μ„œ μ£Όμž…ν• μˆ˜λ„ 있고!?

useEventFormμ΄λΌλŠ” μ• κ°€ λ„ˆλ¬΄ μ±…μž„μ΄ κ±°λŒ€ν•œκ²ƒ κ°™μ•„μ„œ λ‚΄λΆ€μ μœΌλ‘œ 역할을 λΆ„λ¦¬ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 그쀑에 useTimeValidationμ΄λΌλŠ” 훅을 λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. 이 훅은 λ‚΄λΆ€μ μœΌλ‘œ getTimeErrorMessageλ₯Ό μ“°λŠ”λ° ν•΄λ‹Ή μœ ν‹Έμ€ 이미 μœ λ‹›ν…ŒμŠ€νŠΈκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€. 일단 μ €λŠ” 이 getTimeErrorMessage에 λŒ€ν•œ 검증을 useTimeValidationμ—μ„œ ν•˜λŠ”κ²Œ μ˜λ―Έκ°€ μ—†λ‹€κ³  μƒκ°ν•˜λŠ”λ°μš”. κ·Έλ ‡λ‹€λ©΄ useTimeValidationμ—μ„œ ν• μˆ˜μžˆλŠ” ν…ŒμŠ€νŠΈλŠ” κ΅¬ν˜„μ— λŒ€ν•œ λΆ€λΆ„λ§Œ 보면 μƒνƒœ μ—…λ°μ΄νŠΈ, μΈν„°νŽ˜μ΄μŠ€κ΄€λ ¨, ν•Έλ“€λŸ¬λ₯Ό μƒμ„±ν•˜λŠ” 뢀뢄에 λŒ€ν•œ 것 정도가 될거같은데 이런 뢀뢄에 λŒ€ν•œ 검증이 μ˜λ―Έκ°€ μžˆλŠ” κ²ƒμΌκΉŒμš”? μ •λ¦¬ν•˜μžλ©΄ μœ ν‹Έν•¨μˆ˜μ— λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ κ΅¬ν˜„λ˜μ–΄μžˆκ³ , 이λ₯Ό λ‹¨μˆœνžˆ μ΄μš©ν•΄μ„œ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 훅이 μžˆλ‹€κ³  ν•˜λ©΄ 이 훅에 λŒ€ν•œ ν…ŒμŠ€νŠΈκ°€ μœ μ˜λ―Έν•˜λ‹€κ³  μƒκ°ν•˜μ‹œλ‚˜μš”? '였λ₯˜ μƒνƒœμ—μ„œ 정상적인 κ°’μœΌλ‘œ λ³€κ²½ν•˜λ©΄ 였λ₯˜κ°€ μ΄ˆκΈ°ν™”λ˜μ–΄μ•Ό ν•œλ‹€' 와 같이 μƒνƒœ μ—…λ°μ΄νŠΈ μ •λ„λŠ” 검증할 수 μžˆκ² λ‹€λŠ” 생각이 λ“œλŠ”λ°, μ–΄λ””κΉŒμ§€ 검증을 ν•΄μ•Όν•˜λŠ”μ§€ μ’€ λͺ¨ν˜Έν•œ 것 κ°™μŠ΅λ‹ˆλ‹€.

깊게 λ“€μ–΄κ°€μ„œ κ³ λ―Όν•΄λ³΄μžλ©΄.. ν•„μš”ν•œ κ΅¬κ°„λ§Œ μ„ λ³„ν•΄μ„œ κ²€μ¦ν•˜μž! 라고 이야기할 수 μžˆκ² μ§€λ§Œ μ†”μ§νžˆ κ·Έκ±Έ μ„ λ³„ν•˜λŠ” 과정이 더 μ˜€λž˜κ±Έλ¦°λ‹€κ³  μƒκ°ν•΄μš” γ…Žγ…Ž 아싸리 λ‹€ κ²€μ¦ν•΄λ²„λ¦¬λŠ”κ²Œ μ†νŽΈν•˜μ§€ μ•Šλ‚˜? λΌλŠ” μƒκ°μž…λ‹ˆλ‹€. μ €λŠ” κ·Έλƒ₯ λ‹€ κ²€μ¦ν•˜λŠ” νŽΈμ΄λžλ‹ˆλ‹€.

λ‹¨μˆœνžˆ presentation μ»΄ν¬λ„ŒνŠΈλ‘œ λ·°μž‰μ—­ν• μ„ ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈκ°€ μžˆμ„λ•Œ props둜 ν•Έλ“€λŸ¬λ“€μ΄ λ„˜μ–΄μ˜¨λ‹€λŠ” 이유둜 μ–΄λ–€ μ΄λ²€νŠΈκ°€ λ°œμƒν–‡μ„λ•Œ 이벀트 ν•Έλ“€λŸ¬λ₯Ό λͺ¨ν‚Ήν•΄μ„œ ν…ŒμŠ€νŠΈ ν•˜λŠ”κ²Œ μ˜λ―Έκ°€ μžˆμ„κΉŒμš”?

μ œκ°€ μ’…μ’… 이야기 ν–ˆμ—ˆλŠ”λ°, μ €λŠ” μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ ν…ŒμŠ€νŠΈλŠ” 의미 μ—†λŠ” κ²½μš°κ°€ λ§Žλ‹€κ³  μƒκ°ν•΄μš”. μ»΄ν¬λ„ŒνŠΈλŠ” 데이터λ₯Ό ν‘œν˜„ν•˜λŠ” μˆ˜λ‹¨μ΄κ³ , κ·Έλ ‡λ‹€λ©΄ 데이터에 λŒ€ν•΄ 그리고 데이터λ₯Ό λ§Œλ“€μ–΄λ‚΄λŠ” ν•¨μˆ˜μ— λŒ€ν•΄ 검증할 수 μžˆλ‹€λ©΄ μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ 검증도 μ–΄λŠμ •λ„λŠ” λ˜λŠ”κ²Œ μ•„λ‹κΉŒ!? 라고 μƒκ°ν•©λ‹ˆλ‹€.

λ‹€λ§Œ, μ»΄ν¬λ„ŒνŠΈλŠ” μΈν„°λž™μ…˜μ„ μ—°κ²°ν•˜λŠ” μˆ˜λ‹¨μ΄ 될 수 있기 λ•Œλ¬Έμ— μΈν„°λž™μ…˜μ΄ 잘 λ°œμƒν•˜λŠ”κ°€!? μ •λ„λŠ” ν…ŒμŠ€νŠΈν•΄λ³Ό 수 있겠죠 γ…Žγ…Ž 이 κ²½μš°μ—λŠ” κ·Έλƒ₯ e2e ν…ŒμŠ€νŠΈλ₯Ό.... 해보면 μ–΄λ–¨κΉŒ!? 라고 또 연결이 λ˜λ„€μš” γ…‹γ…‹

데이터가 μ „λΆ€ λΉ„μ–΄μ ΈμžˆλŠ” 상황인 κ²½μš°μ— λ‘œλ”©ν›„μ— '일정 λ‘œλ”© μ™„λ£Œ!'와 같은 ν† μŠ€νŠΈ λ©”μ‹œμ§€κ°€ μ—†λ‹€λ©΄ μ–΄λ–»κ²Œ 데이터가 λΆˆλŸ¬μ™€μ‘ŒμŒμ„ μ•Œμ•„ 차릴 수 μžˆμ„κΉŒμš”?

μ΄μ•ΌκΈ°λ“œλ¦° 것 처럼 λͺ© ν•¨μˆ˜λ₯Ό μ΄λ²€νŠΈμ— λ„˜κ²¨μ€€ λ‹€μŒμ— λͺ© ν•¨μˆ˜κ°€ ν˜ΈμΆœλ˜λŠ”μ§€ ν™•μΈν•΄λ³΄λŠ”κ±°μ£ . λ‹€λ§Œ 이건 μ–΄λ–€μ‹μœΌλ‘œ μ½”λ“œκ°€ μž‘μ„±λ˜μ–΄μžˆλŠ”μ§€μ— 따라 λ‹¬λΌμ§ˆ 수 μžˆλ‹€κ³  μƒκ°ν•΄μš”!