HARD
7์ฃผ์ฐจ ๊ณผ์ ์ฒดํฌํฌ์ธํธ
๊ธฐ๋ณธ๊ณผ์
- ์ด 11๊ฐ์ ํ์ผ, 115๊ฐ์ ๋จ์ ํ ์คํธ๋ฅผ ๋ฌด์ฌํ ์์ฑํ๊ณ ํต๊ณผ์ํจ๋ค.
์ง๋ฌธ
Q. handlersUtils์ ๋จ๊ธด ์ง๋ฌธ์ ๋ต๋ณํด์ฃผ์ธ์.
1. ๊ฐ ํ ์คํธ๋ง๋ค ๋ ๋ฆฝ์ ์ธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ค.
๊ฐ ํ ์คํธ๊ฐ ์์ ๋ง์ events ๋ฐฐ์ด์ ๊ฐ์ง
export const setupMockHandlerCreation = (initEvents = [] as Event[]) => {
// ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ๊น์ ๋ณต์ฌ๋ก ๊ฒฉ๋ฆฌ
const events = [...initEvents]; // ํ
์คํธ๋ง๋ค ๋ค๋ฅธ ๋ฐ์ดํฐ ์ฌ์ฉ์ ์ํด
// ... ํธ๋ค๋ฌ ์ค์
2. ๊ฐ ํจ์๋ง๋ค ๋ฑ๋ก๋ ํธ๋ค๋ฌ๋ฅผ ์ด๊ธฐํ ํ ํ ์๋ก์ด ํธ๋ค๋ฌ๋ฅผ ์ค์ ํ๋ค.
// ๊ธฐ์กด ํธ๋ค๋ฌ ์ด๊ธฐํ
server.resetHandlers();
server.use(
// ์๋ก์ด ํธ๋ค๋ฌ ์ค์
);
์ด์ ํ ์คํธ์ ํธ๋ค๋ฌ๊ฐ ๋ค์ ํ ์คํธ์ ์ํฅ์ ์ค ์ ์๊ณ ๋ค๋ฅธ ํ ์คํธ์์ ์์ฑํ ํธ๋ค๋ฌ๊ฐ ํ์ฌ ํ ์คํธ๋ฅผ ๊ฐ์ญํ ์๋ ์์ผ๋ ์ด๊ธฐํ ํ๋ค.
3. ํด๋ก์ ๋ฅผ ํ์ฉ
export const setupMockHandlerUpdating = () => {
// ์ด ํจ์๋ง์ ๋
๋ฆฝ์ ์ธ events ๋ฐฐ์ด
const events = [
createMockEvent(1, { title: '๊ฐ๋ฐ ๊ณต๋ถ' }),
createMockEvent(2, { title: 'ํ์' })
];
server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events }); // ํด๋ก์ ์ events
}),
http.put('/api/events/:id', async ({ request, params }) => {
// ...
// ํด๋ก์ ์ events ๋ฐฐ์ด ์์
events[index] = { ...events[index], ...updatedEvent };
return HttpResponse.json(events[index]);
})
);
- ํจ์ ํธ์ถ๋ง๋ค ์๋ก์ด ์ค์ฝํ๋ฅผ ์์ฑํ๋ค
setupMockHandlerCreation()์ด ํธ์ถ๋ ๋๋ง๋ค ์๋ก์ด events ๋ฐฐ์ด ์์ฑ - ํธ๋ค๋ฌ๊ฐ ์ค์ฝํ๋ฅผ ๊ธฐ์ต
MSW ํธ๋ค๋ฌ๋ค์ด ํจ์ ์คํ ์์ ์ events ๋ฐฐ์ด์ ๊ธฐ์ตํ๋ค - ๋
๋ฆฝ์ ์ธ ์ํ ๋ณด์ฅ
๊ฐ ํ ์คํธ๊ฐ ์์ ๋ง์ events ๋ฐฐ์ด์ ๊ฐ์ง๊ฒ ๋๋ค
๋ฐ๋ผ์ ํ ์คํธ ๋ค์ด ๋์์ ์คํ๋์ด๋ ์๋ก ๋ค๋ฅธ ์ด๋ฒคํธ๋ฅผ ๊ฐ์ง๊ธฐ์ ๋ณ๋ ฌ๋ก ๋์๋ ์์ ์ ์ด๊ฒ ๋์ํ๋ค.
Q. ํ ์คํธ๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ตฌ๋์ํค๊ธฐ ์ํด ์์ฑํ๋ ์ค์ ๋ค์ ์๊ฐํด์ฃผ์ธ์.
MSW (Mock Service Worker) ์ค์
- ๊ฐ ํ ์คํธ๋ง๋ค ๋ ๋ฆฝ์ ์ธ API ์๋ต ์ค์ ํ๋ค
- ํ ์คํธ๋ค์ด ๋์์ ์คํ๋์ด๋ ์๋ก ์ํฅ์ ์ฃผ์ง ์๋๋ค.
/* msw */
export const server = setupServer(...handlers); //๊ฐ์ง ์๋ฒ ์์ฑ ํ๊ณ handlers์ ์ ์๋ API ๋ฑ๋ก
beforeAll(() => {
server.listen(); // ์๋ฒ ์์ํด์ ์์ฒญ ๋ฐ์ ์ค๋น
vi.useFakeTimers({ shouldAdvanceTime: true }); // shouldAdvnceTime: true ๋ก ์ค์ ํ๋ฉด ํ์ด๋จธ๊ฐ ์๋์ผ๋ก ์งํ๋จ
});
beforeEach(() => {
expect.hasAssertions(); //์ต์ ํ๋์ assertion์ด ์๋์ง ํ์ธ, assertion ์์ผ๋ฉด ํ
์คํธ ์คํจ
vi.setSystemTime(new Date('2025-10-01')); // ์ํ๋ ์์ ์ผ๋ก ๊ณ ์
});
afterEach(() => {
server.resetHandlers(); //MSW ํธ๋ค๋ฌ๋ฅผ ์ด๊ธฐํ
vi.clearAllMocks(); //ํน์ ํ
์คํธ ์ผ์ด์ค์์ ์ฌ์ฉ๋ mock ํจ์ ์ด๊ธฐํ
});
afterAll(() => {
vi.resetAllMocks(); //๋ชจ๋ ํ
์คํธ ์ผ์ด์ค์์ ์ฌ์ฉ๋ mock ํจ์ ์ด๊ธฐํ
vi.useRealTimers(); // ํ์ด๋จธ ๊ฐ์ง ํ๊ฒฝ ์ค์ ์ข
๋ฃ
server.close(); //์๋ฒ ์ข
๋ฃ
});
์ฌํ ๊ณผ์
- App ์ปดํฌ๋ํธ ์ ์ ํ ๋จ์์ ์ปดํฌ๋ํธ, ํ , ์ ํธ ํจ์๋ก ๋ถ๋ฆฌํ๋๊ฐ?
- ํด๋น ๋ชจ๋๋ค์ ๋ํ ์ ์ ํ ํ ์คํธ๋ฅผ 5๊ฐ ์ด์ ์์ฑํ๋๊ฐ?
๊ณผ์ ์ ํํ๊ณ
ํ ์คํธ ์ฝ๋๋ฅผ ํด๋ณธ ์ ์ด ์์๋๋ฐ ์ด๋ฒ์ ์ฒ์ ์ง๋ณด๊ฒ ๋์ต๋๋ค. ์ด์ฐจํผ ์์์ผ ํ ๋ด์ฉ์ด๊ณ , ํ์ต์ด ๋ชฉ์ ์ด๋ ๊ทธ๋ฅ hard๋ก ๋์ ํ๋๋ฐ... ๋ฐ๋ผ๊ฐ๋ ค๋ค๊ฐ ๊ฐ๋์ด ์ฐข๊ธฐ๋ ์ค ์์์ต๋๋ค.
์ ๋ ํ ์คํธ๊น์ง๋ ๊ฐ๋จํ๊ณ ๊ด์ฐฎ์๋๋ฐ, timer ์ค์ ์ด๋ผ๋ ๊ฐ ํตํฉ ํ ์คํธ์ ์ด๊ธฐ ์ค์ ๋ถ๋ถ์ ์ ๋ง ์ด๋ ค์ ์ด์, ์ด๋๋ฅผ ์ด๋ป๊ฒ ์๋์ผ ํ ์ง ๊ฐ๋ ์ ์กํ๊ณ ๋ฌธ์๋ง ๋ด์๋ ์ดํด๊ฐ ์ ๋๋ ๋ถ๋ถ๋ ๋ง์ ์์ ๋๊ธฐ๊ฐ ๋ง๋งํ์ต๋๋ค.
๋คํํ ์ฃผ๋ณ์ ๊ณ ์๊ฐ ์์ด์, ์ด๊ฒ์ ๊ฒ ๋ฌผ์ด๊ฐ๋ฉฐ ๊ฒจ์ฐ๊ฒจ์ฐ ์ฌ๊ธฐ๊น์ง ์งํํ์ต๋๋ค. ์ค์ฐ๋ ๊ฐ์ฌํด์........
๊ธฐ์ ์ ์ฑ์ฅ
MSW ๋ ์์๋ณด๊ธฐ
setupServer vs setupWorker
๊ธฐ์กด ๊ณผ์ ์์๋ setupWorker๋ฅผ ์ฌ์ฉํด์ mock ์ ๋ฐ์์ ์งํํ๋ค. ๊ทธ๋ฐ๋ฐ ์ด๋ฒ ๊ณผ์ ๋ setupServer ๋ฅผ ์ฌ์ฉํ๊ฑฐ ๊ฐ๋ค. ๋์ ์ฐจ์ด๋ ๋ญ๊น?
- ํ
์คํธ(Node ํ๊ฒฝ) โ
setupServer()์ฌ์ฉ (Jest, Vitest ๊ฐ์ ์ ๋ํ ์คํธ ํ๊ฒฝ) - ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ ํ๊ฒฝ โ
setupWorker()์ฌ์ฉ (Service Worker ๋ฑ๋กํด์ fetch/XHR ๊ฐ๋ก์ฑ๊ธฐ)
์ฉ๋ ์ฐจ์ด๋ผ๋ ๊ฑด ์๊ฒ ๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ง๊ธ์ setupWorker ์ค์ ์ด ์๋๊ฑฐ ๊ฐ์๋ฐ, ์ด๋ป๊ฒ ํ๋ฉด์์ mock์ ๋ฐ์์ ์ฌ์ฉํ ์ ์๋ ๊ฑธ๊น?
server.js
MSW์ setupWorker ๋์ฒด์ฌ ๊ฐ์ ์ญํ . ๋ธ๋ผ์ฐ์ ์์ ์ง์ Service Worker๋ฅผ ๋ฑ๋กํด์ ์์ฒญ์ ๊ฐ๋ก์ฑ๋ ๋์ , Node/Express ์๋ฒ๋ฅผ ๋์์ /api/events ๋ผ์ฐํธ๋ฅผ ๊ฐ์ง๋ก ๊ตฌํํด๋๊ณ ํ๋ก ํธ์๋๊ฐ ์ฌ๊ธฐ์ fetch/axios ์์ฒญ์ ๋ณด๋ด๋ ๊ตฌ์กฐ๋ค.
- ๋ธ๋ผ์ฐ์ โ
http://localhost:3000/api/events์์ฒญ - Express mock ์๋ฒ โ JSON ํ์ผ(
realEvents.json) ์ฝ๊ณ ์๋ต - ๋ฐ๋ผ์ ํ๋ก ํธ์์๋ ์ค์ API๊ฐ ์์ด๋ "์ง์ง์ฒ๋ผ" ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค.
Express Mock ์๋ฒ vs Worker ์ ์ฐจ์ด๋ ๊ถ๊ธํด์ ์ฐพ์๋ด
- worker๋ ์ธ๋ฉ๋ชจ๋ฆฌ โ ์๋ก๊ณ ์นจ ์ ์ด๊ธฐํ, express๋ ์ง์ง ๋์ด๊ฑฐ๋ผ ํ์ผ(JSON)๋ก ์ง์ ๊ฐ๋ฅ
- server.js๋ก ๋์ ๊ธฐ ๋๋งค ์ธ๋ถ ํด๋ผ์ด์ธํธ ๋ชจ๋ ํธ์ถ ๊ฐ๋ฅ, worker๋ ํด๋น ๋ธ๋ผ์ฐ์ ์์๋ง
์ฆ..์ค์ ์๋ฒ์ฒ๋ผ ์ง์ง ํ๋ด๋ฅผ ๋ผ ์ ์๋ค..
ํ ์คํธ ์ฝ๋ ๋ฌธ๋ฒ
ํ ์คํธ ์ฝ๋ ์์ฑํ๋ฉด์ ์์ฃผ ์ฌ์ฉํ ๋ฌธ๋ฒ ์์ฃผ๋ก ์ ๋ฆฌ
- toBe: "์ด๊ฒ์ดย ์ ํํ ๊ฐ์ย ๊ฒ์ธ๊ฐ?"
- toEqual:ย "์ด๊ฒ์ย ๋ด์ฉ์ดย ๊ฐ์๊ฐ?"
test('toBe vs toEqual', () => {
const a = { value: 1 };
const b = { value: 1 };
expect(a).toEqual(b); // โ
ํต๊ณผ: ๋ด์ฉ์ด ๊ฐ์์ง ํ์ธ โ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ๋ด๋ถ ๊ตฌ์กฐ ๋น๊ต
expect(a).toBe(b); // โ ์คํจ: ๊ฐ์ ๊ฐ์ฒด(์ฐธ์กฐ)์ธ์ง ํ์ธ โ ===์ ๊ฐ์ ์ญํ
});
- within: ํน์ DOM ์์ ๋ด๋ถ์์๋ง ์ฐพ๊ธฐ
render(
<div>
<section aria-label="A"><p>apple</p></section>
<section aria-label="B"><p>banana</p></section>
</div>
);
const sectionA = screen.getByLabelText('A');
expect(within(sectionA).getByText('apple')).toBeInTheDocument();
// DOM ์์์ ์ฟผ๋ฆฌ ๋ฒ์๋ฅผ ์ ํํ ๋ ์ฌ์ฉ
// ๋์ผํ ํ
์คํธ๊ฐ ์ฌ๋ฌ ๊ณณ์ ์์ ๋ ์ ํํ ์์ญ ์ง์ ๊ฐ๋ฅ
- toHaveBeenCalledWith: ํจ์๊ฐ ํน์ ์ธ์๋ก ํธ์ถ๋์๋์ง ํ์ธ
const mockFn = jest.fn();
mockFn('hello', 42);
expect(mockFn).toHaveBeenCalledWith('hello', 42); // โ
expect(mockFn).toHaveBeenCalledWith('bye'); // โ
// mock ํจ์๊ฐ ์ด๋ค ์ธ์์ ํจ๊ป ํธ์ถ๋์๋์ง ๊ฒ์ฆํ ๋ ์ฌ์ฉ
// ํธ์ถ ์์์ ์ธ์ ์กฐํฉ์ ์ ํํ ํ
์คํธํ ์ ์์
- **vi.setSystemTime, vi.advanceTimersByTimeAsync **: ๊ฐ์ง ์๊ฐ ์ค์
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-01'));
// ํ์ฌ ์๊ฐ์ ์ง์ ๋ ๋ ์ง๋ก ์ค์
await vi.advanceTimersByTimeAsync(1000);
// ํ์ด๋จธ ๊ธฐ๋ฐ ์ฝ๋์์ ์๊ฐ ๊ฐ์ ์งํ
์ฝ๋ ํ์ง
ํ
์คํธ ์ฝ๋๋ฅผ ํด๋ณธ ์ ์ด ์์๊ธฐ์ ์ฝ๋์ ํ์ง๋ณด๋ค๋
์ค๋ช
์ด ์ผ๋ง๋ ์ ํํ๊ฐ? ๋ชจํธํ ํํ์ ์๋๊ฐ? ๋ฅผ ์ค์ฌ์ผ๋ก ๊ฒํ ํ์ต๋๋ค.
๋ ๋๋ฝ๋ ํ
์คํธ๋ ์๋์ง, ์๋ฏธ ์๋ ํ
์คํธ๊ฐ ํฌํจ๋์ด ์์ง ์์์ง๋ฅผ ์ดํด๋ณด๋ฉฐ
๋ณด๋ค ์๋ฏธ ์๋ ํ
์คํธ๋ก ๋ฐฉํฅ์ผ๋ก ๋ฐ๊ฟ๋ณด๋ ๊ฒ์ ์ง์คํ์ต๋๋ค.
๋ฌด์๋ฏธํ ํ ์คํธ ์๋ต
- getDaysInMonth
//์ ํจํ์ง ์์ ์์ ๋ํด ์ ์ ํ ์ฒ๋ฆฌํ๋ค < ์๋ ๋ฌธ๊ตฌ
it('์์ด 12๋ฅผ ์ด๊ณผํ๋ฉด ์๋์ผ๋ก ๋ค์ ํด๋ก ๋์ด๊ฐ์ ๊ณ์ฐํ๋ค (2025๋
13์ โ 2026๋
1์)', () => {
const result = getDaysInMonth(2025, 13);
expect(result).toBe(31);
});
ํด๋น ํ ์คํธ๋ ์ ํธํจ์ ํ ์คํธ ๋ณด๋ค๋ Date ๊ฐ์ฒด์ ๋ํ ํ ์คํธ๋ก ๋๊ปด์ ธ ์๋ตํ์ต๋๋ค.
์ฃ์ง ์ผ์ด์ค ์ถ๊ฐ
- getWeeksAtMonth
it('ํ๋
(2025๋
)์ 2์ 28์ผ๋ก ๋๋๋ ์์ ์ฃผ ๋จ์ ๋ฐฐ์ด์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ๋ค', () => {
const weeksAtMonth = getWeeksAtMonth(new Date('2025-02-28'));
expect(weeksAtMonth).toEqual([
[null, null, null, null, null, null, 1],
[2, 3, 4, 5, 6, 7, 8],
[9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22],
[23, 24, 25, 26, 27, 28, null],
]);
});
์ค๋ , ํ๋ ์ด ์ฃ์ง ์ผ์ด์ค๋ผ ์๊ฐ๋์ด ํด๋น ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
ํผ๋์ ์ฌ์ง๊ฐ ์๋ ํํ์ ์ ๊ฑฐ
- getWeekDates
before: ์ฃผ์ ์์(์์์ผ)์ ๋ํด ํด๋น ์ฃผ์ ์ ์ฒด ๋ ์ง๋ค์ ๋ฐํํ๋ค
after: ์์์ผ์ ๋ํด ํด๋น ์ฃผ์ ์ ์ฒด ๋ ์ง๋ค์ ๋ฐํํ๋ค
๊ธฐ์กด ๋ฌธ๊ตฌ์ "์ฃผ์ ์์(์์์ผ)" ํํ์ด ์ค์ ๋ฐํ ๊ฒฐ๊ณผ(์ผ์์ผ ์์)์ ์ด๊ธ๋ ํผ๋์ ์ฌ์ง๊ฐ ์์ด "์์"์ด๋ผ๋ ํํ์ ์ ๊ฑฐํ์ต๋๋ค.
๋ชจํธํ ํํ ๊ตฌ์ฒดํ
- fetchHolidays
before: ์ฃผ์ด์ง ์์ ๊ณตํด์ผ๋ง ๋ฐํํ๋ค
after: 2025๋
3์์ ๊ณตํด์ผ๋ง ๋ฐํํ๋ค
before: ๊ณตํด์ผ์ด ์๋ ์์ ๋ํด ๋น ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค
after: ๊ณตํด์ผ์ด ์๋ 2025๋
2์์ ๋ํด ๋น ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค
- getUpcomingEvents
before: ์ด๋ฏธ ์๋ฆผ์ด ๊ฐ ์ด๋ฒคํธ๋ ์ ์ธํ๋ค
after: 10์ 0๋ถ์ ๊ธฐ์ค์ผ๋ก ์๋ฆผ ์๊ฐ์ด ๋ฒ์์ ์๋ ์ด๋ฒคํธ๋ง ๋ฐํํ๋ค
before: ์๋ฆผ ์๊ฐ์ด ์์ง ๋๋ํ์ง ์์ ์ด๋ฒคํธ๋ ๋ฐํํ์ง ์๋๋ค
after: 10์ 15๋ถ์ ๊ธฐ์ค์ผ๋ก ์ด๋ฏธ ์๋ฆผ์ ๋ณด๋ธ ์ด๋ฒคํธ๋ ์ ์ธํ๋ค
์ถ์์ ์ธ ์ค๋ช ๋ณด๋ค๋ ์ผ์ด์ค์ ๋ง๊ฒ ๊ตฌ์ฒดํํ์ต๋๋ค.
ํ์ต ํจ๊ณผ ๋ถ์
ํ ์คํธ๋ฅผ ์์ฑํ ๋ ๊ณ ๋ คํ ๊ธฐ์ค
1. ๋น์ ์ ๋ฐ์ดํฐ (edge case)๋ ํ ์คํธํ ๊ฒ์ธ๊ฐ?
-
๊ธฐ์ค: ํจ์์ ์ฑ ์๊ณผ ์ฌ์ฉ ๋งฅ๋ฝ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ค
-
์์ธ ์ฒ๋ฆฌ๋ฅผ ์ง์ ๋ด๋นํ๋ ํจ์๋ผ๋ฉด, ์๋ชป๋ ์ ๋ ฅ(์: ์์, ๋น ๊ฐ, ์๋ชป๋ ๋ ์ง ๋ฑ)์ ๋ฐ๋์ ํ ์คํธํด์ผ ํ๋ค.
-
์์ ๋ก์ง์์ ์ ๋ ฅ์ด ๋ณด์ฅ๋๋ ๊ฒฝ์ฐ๋ผ๋ฉด, ํด๋น ํจ์์์๋ ์ ์ ์ผ์ด์ค๋ง ํ ์คํธํด๋ ๋ฌด๋ฐฉํ๋ค.
-
Tip: ํ ๋ด์์ "์ด ํจ์๋ ์ ๋ ฅ ์ ํจ์ฑ๊น์ง ์ฑ ์์ง๋๊ฐ?"์ ๋ํ ํฉ์๊ฐ ์์ผ๋ฉด ์ข์.
2. ๊ฒ์ฆ ์ ํ๋์ฝ๋ฉํ ๊ฒ์ธ๊ฐ, ๋์ ์ผ๋ก ๊ฒ์ฌํ ๊ฒ์ธ๊ฐ?
-
๊ธฐ์ค:
-
expect(result).toHaveLength(3)์ฒ๋ผ ๊ณ ์ ๋ ์ซ์๋ฅผ ์ฐ๋ฉด, ํ ์คํธ๊ฐ ๋ช ํํ๊ณ ์๋๊ฐ ๋ถ๋ช ํ๊ฒ ๋๋ฌ๋จ.
-
expect(result).toHaveLength(events.length)์ฒ๋ผ mock ๋ฐ์ดํฐ์ ์์กดํ๋ฉด, ํ ์คํธ์ ์ ์ฐ์ฑ์ ์๊ธฐ์ง๋ง ํ ์คํธ ์์ฒด์ ์๋ฏธ๊ฐ ์ฝํด์ง ์ ์์.
-
-
์ถ์ฒ:
-
๋จ์ํ ์ ๋ ํ ์คํธ โ ํ๋์ฝ๋ฉ๋ ์ซ์๋ฅผ ์ฌ์ฉํด ์๋๋ฅผ ๋ช ํํ
-
๋ณต์กํ ๋์ ๋ฐ์ดํฐ๋ ์กฐ๊ฑด๋ถ ๊ฒฐ๊ณผ โ mock ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ผ๋ก ์ ์ฐํ๊ฒ
-
3. ํ ์คํธ ์ค๋ช ์ ๋ฒ์ฉ์ ์ผ๋ก vs ๊ตฌ์ฒด์ ์ผ๋ก?
-
๊ธฐ์ค:
-
๋ฒ์ฉ ์ค๋ช : ๊ธฐ๋ฅ์ ์ผ๋ฐ์ ์ธ ๋์์ ์ค๋ช ํ ๋ ์ฌ์ฉ ์) ์ ๋ ฅํ ๋ ์ง ๊ธฐ์ค์ผ๋ก ์ฃผ๊ฐ ์ผ์ ์ ๋ฐํํ๋ค
-
๊ตฌ์ฒด์ ์ค๋ช : ํน์ ์ ๋ ฅ๊ฐ๊ณผ ๊ธฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ช ํํ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ ๋ ์ฌ์ฉ ์) 2025-07-01์ ์ ๋ ฅํ๋ฉด ์์์ผ๋ถํฐ ์ผ์์ผ๊น์ง ๋ฐํํ๋ค
-
-
์ถ์ฒ:
- ๊ตฌ์ฒดํํ ์ ์๋ ์ผ์ด์ค๋ ๊ตฌ์ฒดํ ํด์ ์ ๊ธฐ
๊ณผ์ ํผ๋๋ฐฑ
- ํ ์คํธ ์ฝ๋๋ฅผ ์ฒ์ ์์ฑํ๋ ์ฌ๋๋ ์ฝ๊ฒ ๋ฐ๋ผ๊ฐ ์ ์๋๋ก ๊ฐ์ด๋๊ฐ ์ ๋์ด ์์ด ๋ฐฐ์ฐ๊ธฐ ์์ํ์ต๋๋ค!
- ํนํ ๋์ด๋๋ฅผ ์ ํํ ์ ์๋ ์ ์ด ๋ถ๋ด์ ์ค์ด๋ ๋ฐ ๋์์ด ๋์ด ์ข์์ต๋๋ค.
์ ๋ ๋ฐฐ์์ ๋ชฉ์ ์ผ๋ก hard๋ฅผ ์ ํํ๋๋ฐ ํ์คํ hard๋ ์ ๋ง ๋ง๋งํ๋๋ผ๊ณ ์. ์กฐ๊ธ ๋ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด๋ ๊ด์ฐฎ์ง ์์๊น ์ถ์ต๋๋ค. ํ๋ค๊ฐ ๋๋ฌด ์ด๋ ค์์ ํ์์ ํํธ๋ฅผ ๋ฐ์ ์์ฑํ๋๋ฐ ํํธ๊ฐ ์์๋๋ผ๋ฉด ํผ์์๋ ๊ฐ์ ์ก๊ธฐ ์ด๋ ค์ ์ ๊ฒ ๊ฐ์ต๋๋ค.
๋ฆฌ๋ทฐ ๋ฐ๊ณ ์ถ์ ๋ด์ฉ
useNotifications ํ ์คํธ ์ฝ๋ ์์ฑ ์ค์ ์๊ฐ ์ค์ ๊ด๋ จํด์ ์ดํด๊ฐ ์ ์ ๋๋ ๋ถ๋ถ์ด ์์ด์ ์ง๋ฌธ๋๋ฆฝ๋๋ค.
ํ์ฌ setupTest.ts์์ ์ ์ญ์ผ๋ก ์๊ฐ์ ์ค์ ํ๊ณ ์์ต๋๋ค
//setupTest.ts
beforeEach(() => {
expect.hasAssertions(); //์ต์ ํ๋์ assertion์ด ์๋์ง ํ์ธ, assertion ์์ผ๋ฉด ํ
์คํธ ์คํจ
vi.setSystemTime(new Date('2025-10-01')); // ์ํ๋ ์์ ์ผ๋ก ๊ณ ์
});
๊ทธ๋ฐ๋ฐ useNotifications.spec.ts์์ ์๋ฆผ ์์ฑ ํ ์คํธ๋ฅผ ์์ฑํ ๋ ๊ฐ๋ณ ํ ์คํธ์์ ์๊ฐ์ ๋ค์ ์ค์ ํ์ง ์์ผ๋ฉด ํ ์คํธ๊ฐ ์คํจํฉ๋๋ค
//medium.useNotifications.spec.ts
// ? setupTest์์ ์ค์ ์ ํ๋๋ฐ, ์ฌ๊ธฐ์ ๋ค์ ์ค์ ์ ํ๋๊ฑฐ์ง? ๊ทผ๋ฐ ์ํ๋ฉด ํต๊ณผ๊ฐ ์๋๋ค.
// > ์๊ฐ๊น์ง ์ค์ ํ๋๋ ๋๋ค... ๊ธฐ์กด 2025-10-01์์ 2025-10-01 00:00:00 ์ผ๋ก ์ค์ ์ ๋ฐ๊ฟจ๋ค.
// > ์ด๋ฒ์ ์บ๋ฆฐ๋ ํ
ํ
์คํธ์์ ํฐ์ง... ๊ทธ๋ฅ ์ฌ๊ธฐ์๋ง ์๊ฐ ์ค์ ํ๋ ๊ฑธ๋ก...
//๊ทธ๋ฆฌ๊ณ shouldAdvanceTime: true ๋ก ์ค์ ํ๋ฉด ํ์ด๋จธ๊ฐ ์๋์ผ๋ก ์งํ๋๋ค๋ผ๋ ๋ง์ด ์๋๋ฐ, ์ ์ ํ๋ฅผ๊น..
beforeEach(() => {
// ์๊ฐ์ 2025-10-01 00:00:00์ผ๋ก ์ค์
vi.setSystemTime(new Date('2025-10-01 00:00:00'));
});
-
setupTest์์ 2025-10-01 00:00:00๋ก ๋ฐ๊พธ๋ฉด ํด๋น ํ ์คํธ๋ ํต๊ณผํ์ง๋ง ๋ค๋ฅธ hook ํ ์คํธ๋ค์ด ์คํจํฉ๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ ๊ฐ ํ ์คํธ ํ์ผ์์ ๊ฐ๋ณ์ ์ผ๋ก ์๊ฐ์ ์ค์ ํ๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ผ๊น์? ์๋๋ฉด ์ ์ญ ์ค์ ์ ์กฐ์ ํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์๊น์?
-
shouldAdvanceTime: true ์ต์ ์ด ์๊ฐ์ ์๋์ผ๋ก ํ๋ฅด๊ฒ ํด์ค๋ค๊ณ ์ดํดํ๋๋ฐ, ์ค์ ๋ก๋ advanceTimersByTimeAsync()๋ฅผ ์๋์ผ๋ก ํธ์ถํด์ผ๋ง ํ ์คํธ๊ฐ ํต๊ณผํ์ต๋๋ค. shouldAdvanceTime ์ต์ ์ด ์ ํํ ์ด๋ค ์ญํ ์ ํ๋ ๊ฑด๊ฐ์? ์ธ์ ์๋์ผ๋ก ์๊ฐ์ ์งํ์์ผ์ผ ํ๊ณ ์ด ์ต์ ๋ง์ผ๋ก๋ ์ ์ถฉ๋ถํ์ง ์์ ๊ฑด์ง ๊ถ๊ธํฉ๋๋ค...
ํตํฉ ํ ์คํธ์์ ํ์์กด ์ด์
it('notificationTime์ 10์ผ๋ก ํ๋ฉด ์ง์ ์๊ฐ 10๋ถ ์ ์๋ ํ
์คํธ๊ฐ ๋
ธ์ถ๋๋ค', async () => {
vi.stubEnv('TZ', 'UTC'); // ํ์์กด ์ค์
const events = [
createMockEvent(1, {
title: '๋ง์ง๋ง',
date: '2025-10-01',
description: '๋ง์ง๋ง ์ผ์ ',
startTime: '00:10',
endTime: '00:20',
notificationTime: 10, // 10๋ถ ์ ์๋ฆผ
}),
];
setupMockHandlerCreation(events);
renderApp();
// ๋จผ์ ์ด๋ฒคํธ๊ฐ ํ์๋๋์ง ํ์ธ
expect(await screen.findByText('๋ง์ง๋ง ์ผ์ ')).toBeInTheDocument();
act(() => {
vi.advanceTimersByTime(1000); // 1์ด๋ง ์งํ
});
// ์๋ฆผ์ด ํ๋๋ง ์๋์ง ํ์ธ
expect(screen.getByText('10๋ถ ํ ๋ง์ง๋ง ์ผ์ ์ด ์์๋ฉ๋๋ค.')).toBeInTheDocument();
});
์ด ํ ์คํธ์์๋ง ์๊ฐ์ด 9์๊ฐ์ฉ ์ฐจ์ด๋จ -> ํ์์กด ์ฐจ์ด(9์๊ฐ)๋ก ์๊ฐ๋์ด ์ด ํ ์คํธ์์๋ง vi.stubEnv('TZ', 'UTC') ์ค์
-
ํตํฉ ํ ์คํธ ์งํ์ค notificationTime ํ ์คํธ์์๋ง 9์๊ฐ ์ฐจ์ด๊ฐ ๋ฐ์ํด์ vi.stubEnv('TZ', 'UTC') ์ค์ ์ด ํ์ํ์ต๋๋ค. ์ ์ด ํ ์คํธ๋ง ํ์์กด ๋ฌธ์ ๊ฐ ์๊ธด ๊ฑธ๊น์...
-
์๊ฐ ๊ด๋ จํ ํ ์คํธ ์ฝ๋๊ฐ ์ ์ผ ์ด์๊ฐ ๋ง์์ต๋๋ค. ํ์ด๋จธ ๊ด๋ จ ํ ์คํธ๋ฅผ ์์ฑํ ๋ ํ์ด ์์๊น์? ์๊ฐ ๊ด๋ จ ํ ์คํธ๊ฐ ๋ณต์กํด์ ํจ์จ์ ์ธ ์ ๊ทผ๋ฒ์ ์๊ณ ์ถ์ต๋๋ค.
ํ ์คํธ ์ฝ๋ ์์ฑ์ด ์์ง ์ต์ํ์ง ์์์ ์ ๊ฐ ์ ๊ทผํ๋ ๋ฐฉ์์ด ๋ง๋์ง ํ์ ์ด ์์ง ์์ต๋๋ค. ์กฐ์ธ ๋ถํ๋๋ฆฝ๋๋ค!
๊ณผ์ ํผ๋๋ฐฑ
์๊ณ ํ์ จ์ต๋๋ค! ๋์๋
Q. useNotifications ํ ์คํธ ์ฝ๋ ์์ฑ์ setSystemTime๋ฌธ์
A. ๋ค ์ค์ ์ดํ ๋ฏธ์ธํ ์๊ฐ์ ์ฐจ์ด๊ฐ ์๊ธธ์ ์์ผ๋ beforeEach๋ณด๋ค๋ ์ค์ ํ ์คํธ ์ผ์ด์ค ์ฆ it()ํจ์์์์ ์ํ๋ ์๊ฐ์ ์ต๋ํ ํ์ํ ์์น์์ ์ค์ ํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค :)
Q. shouldAdvanceTime์ ์ญํ
A. ์ ๊ฐ ์์ ์ ์ ๋ฆฌํ๊ฑด shouldAdvanceTime๋ง์ ํ์ด๋จธ๋ฅผ ํ๋ฒ์ ๊ฝํ๊ณ ์๊ฐ์ ์กฐ์ ํ๋ ๊ฒ์ด ์๋๋ผ ์กฐ๊ธ์ฉ ํ๋ฅด๋ ๊ฒ์ ์๋ฎฌ๋ ์ด์ ๋ง ํด์ค๋ฟ ์จ์ ํ๊ฒ ์๊ฐ์ด ํ๋ฅด๊ณ ํ์ด๋จธ์ ์ฝ๋ฐฑ๋ค์ด ์จ์ ํ๊ฒ ์คํ๋๊ฒ ํ๋ ค๋ฉด ์๊ฐ์ ์ญ์ฑ ๋ฐ์ด์ฃผ๋ ์์ ์ด ํ์ํ๋ค๋ก ์ ๋ฆฌํ์ด์. ๊ทธ๊ฒ vi.advanceTimersByTimeAsync()์ฃ . ์ฆ shouldAdvanceTime ์ค์ ์ ๊ทธ๋ฅ ํ์ด๋ฉฐ ์๊ฐ์ด ํ๋ฆ์ ๊ดํ ์ด์ผ๊ธฐ๊ณ ์จ์ ํ ์๊ฐํ ํ๋ฅธ๊ฒ์ ๋ณด์ฅํด ๋ชจ๋ ์ฝ๋ฐฑ์ ์คํํ๋ ๊ฒ์ ๋ณ๊ฐ์ ์ด์ผ๊ธฐ ์ธ๊ฒ์ด์ฃ .
Q. ํตํฉ ํ ์คํธ์์ ํ์์กด ์ด์
A. ์ ์ด๊ฑด ์๋ฒ์์ ์ด์๊ฐ ๋ฐ์ํ๋ค๋ ๋ป์ธ๊ฐ์? ์ ์ด์ํ๋ค์ฅ ํ์์กด ์ด์๋ ๋ณดํต ์์๋๋ฐ์ ๋ญ๊ฐ ์์คํ ์ค์ ์ ํ์์กด์ด ๋ฌ๋ผ์ ์ด์ง ์์๊น์?
Q. ์๊ฐ ๊ด๋ จํ ํ ์คํธ ์ฝ๋๊ฐ ์ ์ผ ์ด์๊ฐ ๋ง์์ต๋๋ค. ํ์ด๋จธ ๊ด๋ จ ํ ์คํธ๋ฅผ ์์ฑํ ๋ ํ
A. ์๋ง ์ฒ์์ด์๊ธฐ ๋๋ฌธ์ ์ ์ํ๊ธฐ ํ๋ค์์ง ์์๋ ์ถ์ด์. ์ฌ์ค vitest์ ๊ฐ์ ํ ์คํธ๋ฌ๋๊ฐ ์ ๊ณตํ๋ api ์ ์ด ๊ทธ๋ ๊ฒ ๋ง์ง ์๊ธฐ ๋๋ฌธ์ ์ ์๊ฐ์๋ ์ด๋ฒ์ ํ๋ฒ ์ต์ํด์ง์๋ฉด ๋ค์๋ถํฐ๋ ํฌ๊ฒ ์ด๋ ต์ง ์์ ๊ฒ ๊ฐ์์. ๊ด๋ จํด์ ๋ง์ฐ์ค ๋๋๊ทธ ๊ฐ์ ํ ์คํธ๋ ์ฒ์์๋ ์ด๋ป๊ฒ ํ๋ผ๋๊ฑด์ง ๋๊ฐํ๋ฐ์. ๋ช๋ฒ ์ํ์ฐฉ์ค๋ฅผ ๊ฒช๊ณ ๋๋ฉด ๋ณ๊ฒ ์๋๊ฒ ๋ฉ๋๋ค. :) ์ฌ์ค ์ ๋ ์ด๋ค ์ ํ์ ํ ์คํธ๋ฅผ ์ค๋๋ง์ ํ๋ฉด ํ๋์ ํด๋งค์ ใ ใ