HARD
7์ฃผ์ฐจ ๊ณผ์ ์ฒดํฌํฌ์ธํธ
๊ธฐ๋ณธ๊ณผ์
- ์ด 11๊ฐ์ ํ์ผ, 115๊ฐ์ ๋จ์ ํ ์คํธ๋ฅผ ๋ฌด์ฌํ ์์ฑํ๊ณ ํต๊ณผ์ํจ๋ค.
์ง๋ฌธ
Q. handlersUtils์ ๋จ๊ธด ์ง๋ฌธ์ ๋ต๋ณํด์ฃผ์ธ์.
์ฉ๋์ ๋ฐ๋ผ ๊ฐ๋ณ์ ์ผ๋ก ๋ถ๋ฆฌ๋์ด์๋ ํจ์๋ฅผ createMockHandlers๋ผ๋ ํ๋์ ์ ํธ๋ฆฌํฐ ํจ์๋ก ํตํฉํ์ต๋๋ค. ์ด ํฉํ ๋ฆฌ ํจ์๋ ํด๋ก์ (Closure)๋ฅผ ํ์ฉํ์ฌ, ๊ฐ ํ ์คํธ๊ฐ ๊ณ ์ ํ ์ํ๋ฅผ ๊ฐ๋ ํธ๋ค๋ฌ๋ฅผ ์์ฑํฉ๋๋ค.
export const createMockHandlers = (initEvents = [] as Event[]) => {
let testEvents = [...initEvents];
const handlers = [
http.get('/api/events', () => {
return HttpResponse.json({ events: testEvents });
}),
http.post('/api/events', async ({ request }) => {
const event = (await request.json()) as Event;
const newEvent = { ...event, id: crypto.randomUUID() };
testEvents.push(newEvent);
return HttpResponse.json(newEvent);
}),
http.put('/api/events/:id', async ({ request, params }) => {
const event = (await request.json()) as Event;
const { id } = params;
testEvents = testEvents.map((e) => (e.id === id ? event : e));
return HttpResponse.json(event);
}),
http.delete('/api/events/:id', async ({ params }) => {
const { id } = params;
testEvents = testEvents.filter((event) => event.id !== id);
return HttpResponse.json(id);
}),
];
const getEvents = () => {
return testEvents;
};
const reset = () => {
testEvents = [...initEvents];
};
return { handlers, reset, getEvents };
};
[์ฌ์ฉ ์์]
it("๋คํธ์ํฌ ์ค๋ฅ ์ '์ผ์ ์ญ์ ์คํจ'๋ผ๋ ํ
์คํธ๊ฐ ๋
ธ์ถ๋๋ฉฐ ์ด๋ฒคํธ ์ญ์ ๊ฐ ์คํจํด์ผ ํ๋ค", async () => {
const { handlers } = createMockHandlers(initialEventData);
server.use(...handlers);
...
[์ฃผ์ ๊ตฌํ]
createMockHandlers(initialState): ์ด๊ธฐ ์ํ๋ฅผ ์ธ์๋ก ๋ฐ์, ํด๋น ์ํ์๋ง ์ ๊ทผํ๋ ํธ๋ค๋ฌ ๋ฐฐ์ด๊ณผ ์ํ ์ ์ด ํจ์(reset, getEvents)๋ฅผ ๋ฐํํฉ๋๋ค.handlers: ์์ฑ๋ ์ค์ฝํ์ ์ํ๋ฅผ ์กฐ์ํ๋ MSW ์์ฒญ ํธ๋ค๋ฌ ๋ฐฐ์ด์ ๋๋ค.reset(): ํธ๋ค๋ฌ์ ์ํ๋ฅผ initialState๋ก ์ด๊ธฐํํ์ฌ ํ ์คํธ ๊ฐ ์ํ ์ค์ผ์ ๋ฐฉ์งํฉ๋๋ค.getEvents(): ํ ์คํธ ์ค ์ํ๋ฅผ ํ์ธํ๊ณ ๊ฒ์ฆ(assertion)ํ ์ ์๋๋ก ํ์ฌ ์ํ๋ฅผ ๋ฐํํฉ๋๋ค.
๊ตฌํํ๊ธฐ ์ , ๋ ๊ฐ์ง ์ ๊ทผ ๋ฐฉ์์ ๊ณ ๋ คํ์ต๋๋ค.
1. ๋ ๋ฆฝ์ ์ธ MSW ์๋ฒ ์ธ์คํด์ค๋ฅผ ๋ง๋ ๋ค.
[๊ตฌํ ๋ฐฉ์]
export const createIsolatedTestServer = () => {
const isolatedServer = setupServer();
const testEvents: Event[] = [];
isolatedServer.use(
http.get('/api/events', () => {
return HttpResponse.json([...testEvents]);
})
// ... ๊ธฐํ ํธ๋ค๋ฌ
);
return { server: isolatedServer, events: testEvents };
};
[์ฅ์ ]
- ์๋ฒฝํ ๊ฒฉ๋ฆฌ๋ฅผ ๋ณด์ฅํ์ฌ ํ ์คํธ ๊ฐ ์ํธ์์ฉ์ ์์ฒ์ ์ผ๋ก ์ฐจ๋จํฉ๋๋ค.
[๋จ์ ]
- ๊ฐ ํ ์คํธ๋ง๋ค ์์ฑํ๊ณ ๊ด๋ฆฌํด์ผ ํ๋ฏ๋ก ๋ณต์ก์ฑ์ด ์ฆ๊ฐํฉ๋๋ค.
- ํ ์คํธ ์ค์ํธ๊ฐ ์ปค์ง์๋ก ์ฑ๋ฅ ์ ํ ๋ฐ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
2. ํด๋ก์ ํ์ฉํ ์ํ ๊ฒฉ๋ฆฌ (ํ์ฌ ๋ฐฉ์)
ํธ๋ค๋ฌ๋ฅผ ์์ฑํ๋ ํฉํ ๋ฆฌ ํจ์ ๋ด์ ์ํ๋ฅผ ์ง์ญ ๋ณ์๋ก ๋์ด, ํด๋ก์ ๋ฅผ ํตํด ์ํ๋ฅผ ์บก์ํํ๊ณ ๊ฒฉ๋ฆฌํฉ๋๋ค.
[์ฅ์ ]
- ๊ตฌํ์ด ๊ฐ๋จํ๊ณ ์ง๊ด์ ์ ๋๋ค.
- ๊ธฐ์กด MSW ์๋ฒ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์ ํ์ ์ฐ๋ ค๊ฐ ์ ์ต๋๋ค.
- ํ ์คํธ๋ณ ๋ ๋ฆฝ์ ์ธ ์ํ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ๋น ๋ฅธ ํ ์คํธ ์คํ
[๋จ์ ]
- ์๋ชป ์ฌ์ฉ ์ ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ๋ฅ์ฑ
[์ ํ ๊ธฐ์ค ๋ฐ ํธ๋ ์ด๋ ์คํ]
- ๋จ์์ฑ๊ณผ ์ค์ฉ์ฑ์ ์ด์ ์ ๋ง์ถ์์ต๋๋ค.
- ํด๋ก์ ๋ฅผ ํ์ฉํ ๋ฐฉ๋ฒ์ ์ถฉ๋ถํ ์์ค์ ๊ฒฉ๋ฆฌ๋ฅผ ๊ฐ๋จํ๊ณ ์ง๊ด์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ ์ ์๋ค๊ณ ํ๋จํ์ต๋๋ค. beforeEach์ afterEach ๊ฐ์ ํ ์คํธ ์๋ช ์ฃผ๊ธฐ ํ (hook)๊ณผ ๊ฒฐํฉํ์ฌ ๋ช ํํ ํจํด์ผ๋ก ์ฌ์ฉํ๋ค๋ฉด, ์ค์ํ ์ฌ์ง๋ฅผ ์ต์ํํ๋ฉด์ ํ ์คํธ์ ๋ ๋ฆฝ์ฑ์ ํจ๊ณผ์ ์ผ๋ก ํ๋ณดํ ์ ์์ต๋๋ค
- ํธ๋ค๋ฌ ์์ฑ๊ณผ ๋ฑ๋ก์ ๋ช ํํ ๋ถ๋ฆฌํ๊ณ ์ ํ์ต๋๋ค.
Q. ํ ์คํธ๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ตฌ๋์ํค๊ธฐ ์ํด ์์ฑํ๋ ์ค์ ๋ค์ ์๊ฐํด์ฃผ์ธ์.
1. Setup๊ณผ Teardown ์ ์ฉ
let mockHandlers: ReturnType<typeof createMockHandlers>;
beforeEach(() => {
mockHandlers = createMockHandlers();
server.use(...mockHandlers.handlers);
});
afterEach(() => {
mockHandlers.reset();
server.resetHandlers();
vi.clearAllMocks();
});
๊ฐ ํ ์คํธ๊ฐ ์ด์ ํ ์คํธ์ ์ํ์ ์ํฅ ๋ฐ์ง ์๊ณ , ํญ์ ๋ ๋ฆฝ์ ์ธ ํ๊ฒฝ์์ ์คํ๋๋๋ก ๋ณด์ฅํ๊ธฐ ์ํด beforeEach์ afterEach ์๋ช ์ฃผ๊ธฐ ํ ์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ์ต๋๋ค.
-
beforeEach(๊ฐ ํ ์คํธ ์คํ ์ง์ )mockHandlers = createMockHandlers()๋งค ํ ์คํธ๊ฐ ์์๋๊ธฐ ์ง์ , createMockHandlers๋ฅผ ํธ์ถํ์ฌ ๊ณ ์ ํ ๋ด๋ถ ์ํ๋ฅผ ๊ฐ์ง ์์ ํ ์๋ก์ด ๋ชฉ ํธ๋ค๋ฌ ์ธํธ๋ฅผ ๋ง๋ญ๋๋ค. ๋ฐ๋ผ์ ์ด์ ํ ์คํธ์์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ ํ์ฌ ํ ์คํธ์ ์ ๋ ์ํฅ์ ์ค ์ ์์ต๋๋ค.server.use(...)์์ฑ๋ ํธ๋ค๋ฌ๋ฅผ MSW ์๋ฒ์ ์ ์ฉํ์ฌ ํ์ฌ ํ ์คํธ๊ฐ ์ด ๊ฒฉ๋ฆฌ๋ ํธ๋ค๋ฌ๋ง์ ์ฌ์ฉํ๋๋ก ์ค์ ํฉ๋๋ค.
-
afterEach(๊ฐ ํ ์คํธ ์คํ ์งํ)mockHandlers.reset()ํ์ฌ ํ ์คํธ์์ ์ฌ์ฉํ ํธ๋ค๋ฌ์ ์ํ๋ฅผ ๋ช ์์ ์ผ๋ก ์ด๊ธฐํํ์ฌ ๋ฆฌ์ ํฉ๋๋ค.server.resetHandlers()MSW ์๋ฒ์ ๋ฑ๋ก๋ ํธ๋ค๋ฌ๋ฅผ ๋ชจ๋ ์ ๊ฑฐํ์ฌ ์๋ฒ ์ธ์คํด์ค ์์ฒด๋ฅผ ๊นจ๋ํ ์ํ๋ก ๋๋๋ฆฝ๋๋ค.vi.clearAllMocks()๋ชจ๋ ๋ชจ์(mock) ํจ์์ ํธ์ถ ๊ธฐ๋ก์ ์ง์ ๋ค์ ํ ์คํธ์ ํธ์ถ ํ์ ๊ฒ์ฆ(assertion)์ด ์ ํํ๊ฒ ์ด๋ฃจ์ด์ง๋๋ก ํฉ๋๋ค.
2. renderProvider
export function renderWithProvider(
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
): RenderResult {
return render(ui, {
wrapper: ({ children }) => <Provider>{children}</Provider>,
...options,
});
}
[์ฌ์ฉ ์์]
it('์ด๋ฒคํธ ๋ชฉ๋ก์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋๋ค', () => {
renderWithProvider(<EventList {...defaultProps} />);
์์์ ์ธ๊ธํ ์๋ช ์ฃผ๊ธฐ ํ ์ด ํ ์คํธ ์ธ๋ถ ํ๊ฒฝ(์๋ฒ, ๋ชจํน)์ ๊ด๋ จํ ์ ์ฉ์ด๋ผ๋ฉด, renderWithProvider๋ ํ ์คํธ ๋์ ์ปดํฌ๋ํธ์ ๋ด๋ถ ์คํ ํ๊ฒฝ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ตฌ์ฑํ๋ ์ญํ ์ ํฉ๋๋ค.
- ์ผ๊ด๋ ๋ ๋๋ง ํ๊ฒฝ ์ ๊ณต
- ํ ์คํธํ๋ ค๋ ์ปดํฌ๋ํธ(ui)๊ฐ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ฒ๋ผ ํ์ํ Provider (์: Redux, React Query, ThemeProvider ๋ฑ) ๋ด๋ถ์์ ๋ ๋๋ง๋๋๋ก ์ผ๊ด๋ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
- ์ด ์ ํธ๋ฆฌํฐ ํจ์ ์์ด ์ด๋ค ํ ์คํธ์์๋ Provider๋ฅผ ๊ฐ์ธ๊ณ , ๋ค๋ฅธ ํ ์คํธ์์๋ ๊ฐ์ธ์ง ์๋๋ค๋ฉด ํ ์คํธ๋ ๋ถ์์ ํด์ง๋๋ค. renderWithProvider๋ก ๋ชจ๋ ํ ์คํธ๊ฐ ๋์ผํ ํ๊ฒฝ์์ ์์ํ๋ ๊ฒ์ ๋ณด์ฅํ์ฌ ์์ธก ๊ฐ๋ฅ์ฑ์ ๋์ผ ์ ์์ต๋๋ค.
- ์ํ(State)์ ๊ฒฉ๋ฆฌ
- Provider๋ ๋ณดํต ์ฑ์ ์ ์ญ ์ํ(global state)๋ฅผ ๋ค๋ฃน๋๋ค. ๋ง์ฝ ์ฌ๋ฌ ํ
์คํธ๊ฐ ํ๋์ ๊ณต์ ๋
Provider์ํ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ํ ํ ์คํธ์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ์ ๋ ๋ค์ ํ ์คํธ์ ์ํฅ์ ๋ฏธ์ณ ํ ์คํธ์ ๋ ๋ฆฝ์ฑ์ด ๊นจ์ง๊ฒ ๋ฉ๋๋ค. - renderWithProvider๋ ๊ฐ ํ
์คํธ๊ฐ ํธ์ถ๋ ๋๋ง๋ค ๋ด๋ถ์ ์ผ๋ก ์๋ก์ด
Provider์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค. ์ด Provider๊ฐ ์ํ ๊ด๋ฆฌ๋ฅผ ์๋ก ์์ฑํ๋๋ก ๊ตฌ์ฑ๋์ด ์๊ธฐ ๋๋ฌธ์ ๊ฐ ํ ์คํธ๋ ๊ฒฉ๋ฆฌ๋ ์ํ ์ ์ฅ์๋ฅผ ๊ฐ์ง๊ณ ์์ํ๊ฒ ๋ฉ๋๋ค.
3. ํฉํ ๋ฆฌ ํจํด์ ํ์ฉํ ์ด๋ฒคํธ ๋ฐ์ดํฐ ์์ฑ
export const createTestEvent = (overrides: Partial<Event> = {}): Event => {
const baseEvent = events.events[0];
return { ...baseEvent, ...overrides } as Event;
};
- ๋ฐ๋ณต๋๋ ๋ชฉ ์ด๋ฒคํธ ์์ฑ์ ์ค๋ณต์ ์ค์ด๊ณ ์ ํฉํ ๋ฆฌ ํจ์๋ฅผ ์ ์ฉํ์ต๋๋ค.
- createTestEvent ๊ธฐ๋ณธ ์ด๋ฒคํธ ๊ฐ์ฒด์ overrides๋ฅผ ์ ์ฉํ์ฌ ๋งค๋ฒ ์๋ก์ด ํ ์คํธ ๋ฐ์ดํฐ ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค.
- ๊ฐ ํ ์คํธ๊ฐ ๊ณ ์ ํ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ํ ์คํธ ๊ฐ ์ํธ ๊ฐ์ญ ๋ฐ ๋ถ์์ฉ์ ๋ฐฉ์งํฉ๋๋ค (๋ ๋ฆฝ์ฑ ๋ณด์ฅ)
- ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ณ๊ฒฝ ์, ํฉํ ๋ฆฌ ํจ์๋ง ์์ ํ๋ฉด ๋ชจ๋ ๊ด๋ จ ํ ์คํธ์ ๋ฐ์๋์ด ์ ์ง๋ณด์์ ์ฉ์ดํฉ๋๋ค.
- ํ ์คํธ ์ฝ๋์์ ๋ฐ์ดํฐ ์์ฑ์ ๋ณต์ก์ฑ์ด ์ค์ด๋ค์ด ํ ์คํธ์ ํต์ฌ ๋ก์ง์ ํ์ ํ๊ธฐ ์ฉ์ดํฉ๋๋ค.
์ฌํ ๊ณผ์
- App ์ปดํฌ๋ํธ ์ ์ ํ ๋จ์์ ์ปดํฌ๋ํธ, ํ , ์ ํธ ํจ์๋ก ๋ถ๋ฆฌํ๋๊ฐ?
- ํด๋น ๋ชจ๋๋ค์ ๋ํ ์ ์ ํ ํ ์คํธ๋ฅผ 5๊ฐ ์ด์ ์์ฑํ๋๊ฐ?
๊ณผ์ ์ ํํ๊ณ
์ ๋ ํ ์คํธ์ฝ๋์ ๋ํ ๊ฒฝํ์ด playwright ์ผ๋ก ์ ์ ์๋๋ฆฌ์ค ๋ช ๊ฐ ์์ฑํด๋ณธ ๊ฒฝํ๊ณผ ์ ๋ ํ ์คํธ๋ฅผ ai๋ก ์์ฑํด๋ณธ ๊ฒฝํ ๋ฐ์ ์์์ต๋๋ค. ๊ทธ๋์ ์ด๋ฒ ๊ณผ์ ์์ ์ ์ฉ๋ณด๋ค๋ ์ฉ์ด, ๊ฐ๋ ์์ฃผ๋ก ์ดํด ๋จผ์ ํ๊ณ ์ค์ต์ ํ๊ณ ์ ํ์ต๋๋ค. ์ญ์๋ ์ง๋๊ฐ ๋๋๊ฒ ๋์๊ณ , ์ฒ์์๋ ์ด๋ค ํ๋ฆ์ผ๋ก ์์ฑํด์ผํ ์ง ๊ฐ๋ ์์กํ์ต๋๋ค. e2e๋ ์ ๊ฐ ์ฌ์ฉ์๋ก์์ ๊ฒฝํ๋ ์๊ธฐ์ "์ด๋ค ํ๋ฆ์ผ๋ก ์์ฑํ๋ฉด ๋๊ฒ ๋ค" ๋ผ๋ ๊ฐ๊ฐ์ด๋ผ๋ ์์์ง๋ง, ๋จ์ ํ ์คํธ๋ ๊ทธ ๋ก์ง์ ํ๋ฆ์ ์ ํํ๊ฒ ์ดํดํด์ผ ํ๊ณ , ๊ทธ ์์ฒด๋ฅผ ๊ฒ์ฆํ๋ ๋ก์ง์ ์๊ฐํ๊ณ ๊ตฌํํ๋ค๊ณ ํด๋ ๊ทธ๊ฒ์ ๋ํ ํ์ ์ด ๋ถ์กฑํ์ต๋๋ค. ์งํํ๋ฉด์๋ "์ด๊ฒ ๊ณผ์ฐ ์ด ํจ์๋ฅผ ๊ฒ์ฆํ๋ ํ ์คํธ์ธ๊ฐ? ์ ์๋ฏธํ๊ฐ?" ๋ฅผ ๊ณ์ ์๊ฐํ์ง๋ง, ํ์ ์ ์์ด ์ด๋ ต๊ฒ ๋๊ปด์ก์ต๋๋ค. ํ์์ผ์ ์คํ์ฝ์น๋๊ป์ ์ฃผ์ ๊ฐ์๊ฐ ๋์์ด ๋ง์ด ๋์์ง๋ง ์๊ฐ์ ๋น์ฅ ํ์ํ ๋ถ๋ถ๋ง ๋์๋์ ๋ณธ ๊ฒ์ด ์์ฌ์ ์ต๋๋ค. ๊ฐ์๋ฅผ ๋ค ๋ณด๋ฉด ์ ๋ฆฌ๊ฐ ๋ ๊ฒ ๊ฐ์๋ฐ, ์ ์ดํด๋์ง ์์ ์ํ์์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๊ฒ์ด ์์ฌ์ ์ต๋๋ค.
๊ธฐ์ ์ ์ฑ์ฅ
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ์๋กญ๊ฒ ์๊ฒ ๋ ๊ฐ๋ ๋ค์ ํ๋์ฉ ์ ๋ฆฌํ์ต๋๋ค.
React Testing Library & Jest ์ฃผ์ ๊ฐ๋ ์ฌํ ์ ๋ฆฌ
1. describe
- Jest ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด
describe๋ "ํจ๊ป ํ ์คํธํ ๊ด๋ จ ํ ์คํธ๋ค์ ๊ทธ๋ฃนํํ๊ธฐ ์ํ ๋ธ๋ก"์ ์์ฑํฉ๋๋ค. ๊ทธ๋ฃนํ๋ ํ ์คํธ๋ค์describe๋ธ๋ก์ด ์คํ๋ ๋ ํ๋์ ์ปจํ ์คํธ๋ฅผ ๊ณต์ ํฉ๋๋ค.
[์์]
describe('๋ก๊ทธ์ธ ํผ ํ
์คํธ', () => {
it('ํผ ์์๋ค์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋์ด์ผ ํ๋ค', () => {
// ํ
์คํธ ์ฝ๋...
});
it('ํผ ์ ์ถ ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ ํ๋ค', () => {
// ํ
์คํธ ์ฝ๋...
});
});
describe๋ ์ฌ๋ฌ ๊ฐ์ ํ ์คํธ๋ฅผ ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ด์ฃผ๋ฏ๋ก, ํ ์คํธ ๊ฒฐ๊ณผ ๋ณด๊ณ ์๋ฅผ ๋ณผ ๋ ๊ฐ๋ ์ฑ์ด ๋์์ง๊ณ ์คํจํ ํ ์คํธ๊ฐ ์ด๋ค ๊ธฐ๋ฅ๊ณผ ๊ด๋ จ ์๋์ง ์ฝ๊ฒ ํ์ ํ ์ ์์ต๋๋ค.
2. it / test
- Jest ๊ณต์ ๋ฌธ์์์
it๊ณผtest๋ ์๋ก์ ๋ณ์นญ(alias)์ด๋ฉฐ, ๊ธฐ๋ฅ์ ์ผ๋ก ์์ ํ ๋์ผํฉ๋๋ค. - ๊ถ์ฅ ์ฌ์ฉ๋ฒ: ๊ด์ต์ ์ผ๋ก
describe๋ธ๋ก ์์์it์ ์ฌ์ฉํด "it should do something"๊ณผ ๊ฐ์ ์์ด ๋ฌธ์ฅ์ฒ๋ผ ํ ์คํธ ์ค๋ช ์ ์์ฑํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ํ์ง๋งtestํค์๋๋ฅผ ์ฌ์ฉํด "test if..."์ฒ๋ผ ๋ช ํํ๊ฒ ํ ์คํธ๋ฅผ ์ ์ํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด๋ค ๊ฒ์ ์ฌ์ฉํ๋ ํ ๋ด์์ ์ผ๊ด์ฑ์ ์ ์งํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
3. renderHook
- React Testing Library๋ ์ปดํฌ๋ํธ ๋ ๋๋ง์ ํนํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ง๋ง,
renderHook์ DOM ์์ ์์ด ์ปค์คํ ํ ์ ๋ก์ง๋ง์ ํ ์คํธํ ์ ์๋๋ก ํน๋ณํ ์ ๊ณต๋๋ ๊ธฐ๋ฅ์ ๋๋ค. ํ ์ ๋ฐํ๊ฐ์ด๋ ์ํ ๋ณํ๋ฅผ ํ ์คํธํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. - ์ฃผ์ ์ฉ๋:
useSWR์ฒ๋ผ ๋น๋๊ธฐ ๋ฐ์ดํฐ ํจ์นญ ๋ก์ง์ ๊ฐ์ง ํ ์ด๋, ๋ณต์กํ ์ํ ๊ด๋ฆฌ ๋ก์ง์ ๊ฐ์ง ํ ์ ํ ์คํธํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
4. wrapper
renderํจ์์ ์ต์ ์ผ๋ก ์ ๊ณต๋๋wrapper๋ ํ ์คํธํ ์ปดํฌ๋ํธ๋ฅผ ํน์ ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ๋ ์ญํ ์ ํฉ๋๋ค. ํนํContext Provider๋ThemeProvider์ฒ๋ผ ํ ์คํธ ๋์ ์ปดํฌ๋ํธ๊ฐ ํน์ ์์ ์ปดํฌ๋ํธ์ ์์กดํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉ๋ฉ๋๋ค.- ์ฅ์ :
wrapper์ต์ ์ ์ฌ์ฉํ๋ฉด ๊ฐ ํ ์คํธ ์ผ์ด์ค๋ง๋ค ๋์ผํ Provider๋ฅผ ๋ฐ๋ณตํด์ ์์ฑํ๋ ๋ถํ์ํ ์ฝ๋๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
5. baseElement
renderํจ์์ ์ต์ ์ค ํ๋๋ก, ์ฟผ๋ฆฌ๋ฅผ ๊ฒ์ํ DOM์ ๊ธฐ๋ณธ ์์๋ฅผ ์ง์ ํฉ๋๋ค.- ์ฃผ์ ์ฉ๋:
document.body๊ฐ ์๋ ํน์ ์ปจํ ์ด๋ ์์์๋ง ์์๋ฅผ ์ฐพ์์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ชจ๋ฌ์ด๋ ํ์ ์ปดํฌ๋ํธ๋document.body์ ์ง์ ์์์ด ์๋ ์ ์์ผ๋ฏ๋ก, ํด๋น ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๋ ๋ถ๋ชจ ์์๋ฅผbaseElement๋ก ์ง์ ํ์ฌ ํ ์คํธํ ์ ์์ต๋๋ค.
6. toBe vs toEqual vs toStrictEqual
- Jest์ ๋งค์ฒ(Matcher)๋ค์ ๊ฐ์ ๋น๊ต ๋ฐฉ์์ ๋ฐ๋ผ ์๊ฒฉ๋๊ฐ ๋ค๋ฆ
๋๋ค.
toBe: ์์ ํ์ (์ซ์, ๋ฌธ์์ด, ๋ถ๋ฆฌ์ธ)์ **๋์ผ์ฑ(===)**์ ํ์ธํฉ๋๋ค. ๊ฐ์ฒด๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๊น์ง ๋น๊ตํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๊ฐ์ฒด๋ผ๋ฉด ๋ด์ฉ์ด ๊ฐ์๋ ์คํจํฉ๋๋ค.toEqual: ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ๋ด์ฉ(๊ฐ)์ ์ฌ๊ท์ ์ผ๋ก ๋น๊ตํฉ๋๋ค.expect({ a: 1 }).toEqual({ a: 1 })์ ํต๊ณผํ์ง๋ง,expect({ a: 1 }).toBe({ a: 1 })์ ์คํจํฉ๋๋ค.toStrictEqual:toEqual๋ณด๋ค ๋ ์๊ฒฉํ ๋น๊ต๋ฅผ ์ํํฉ๋๋ค. ๊ฐ์ฒด์ ๋ด์ฉ๋ฟ๋ง ์๋๋ผ ์์ฑ์undefined์ฌ๋ถ, ๋ฐฐ์ด์ ํฌ์์ฑ(sparse array) ๋ฑ๊น์ง ์ ํํ๊ฒ ๋น๊ตํฉ๋๋ค. ์๋ฅผ ๋ค์ด,expect([1, undefined]).toStrictEqual([1])์ ์คํจํฉ๋๋ค.
7. act
- React ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด
act๋ "๋ ๋๋ง ๋ฐ ์ํ ์ ๋ฐ์ดํธ์ ๊ด๋ จ๋ ๋ชจ๋ ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋๋ก ๋ณด์ฅ"ํ๋ ํฌํผ ํจ์์ ๋๋ค. React๋ ๋ด๋ถ์ ์ผ๋ก ์ฌ๋ฌ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐฐ์น(batch)ํ์ฌ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฏ๋ก, ๋น๋๊ธฐ ์์ ํ UI๊ฐ ์ ๋ฐ์ดํธ๋ ๋act๋ก ๊ฐ์ธ์ฃผ์ง ์์ผ๋ฉด ํ ์คํธ๊ฐ ๋ถ์์ ํด์ง ์ ์์ต๋๋ค. - ํต์ฌ:
render์fireEvent๊ฐ์ React Testing Library ํจ์๋ค์ ์ด๋ฏธ ๋ด๋ถ์ ์ผ๋กact๋ก ๊ฐ์ธ์ ธ ์๊ธฐ ๋๋ฌธ์, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ง์ act๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ต๋๋ค. ํ์ง๋งsetTimeout, ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋น๋๊ธฐ ํจ์, ํน์ ์ฌ์ฉ์ ์ ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ React ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธ๋ถ์์ ๋ฐ์ํ๋ ๋น๋๊ธฐ ์ํ ๋ณํ๋ฅผ ํ ์คํธํ ๋๋ ๋ช ์์ ์ผ๋กawait act(...)๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
8. findBy...
findBy๋ ๋น๋๊ธฐ์ ์ผ๋ก ์์๋ฅผ ์ฐพ๋ ์ฟผ๋ฆฌ ํจ์์ ๋๋ค. Testing Library ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉดfindBy๊ณ์ด์getBy์ฟผ๋ฆฌ์waitFor์ ํธ๋ฆฌํฐ๋ฅผ ๊ฒฐํฉํ ๊ธฐ๋ฅ์ ๋๋ค.- ํน์ง:
- Promise๋ฅผ ๋ฐํํ๋ฉฐ, ์์๊ฐ ๋ํ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
- ์ง์ ๋ ๊ธฐ๋ณธ ํ์์์(1000ms) ๋ด์ ์์๋ฅผ ์ฐพ์ง ๋ชปํ๋ฉด Promise๊ฐ
reject๋ฉ๋๋ค.
- ์ฃผ์ ์ฉ๋: API ํธ์ถ ํ ๋ฐ์ดํฐ๊ฐ ํ๋ฉด์ ๋ํ๋๋ ๊ฒ์ ๊ธฐ๋ค๋ฆฌ๊ฑฐ๋, ์ ๋๋ฉ์ด์ ์ด ๋๋ ํ UI๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒ์ ํ ์คํธํ ๋ ์ ์ฉํฉ๋๋ค.
ํ ์คํธ ์์ฑ ์ ๊ผญ ์๊ณ ์์ด์ผ ํ ๊ธฐ๋ณธ ์์น
React Testing Library์ ํต์ฌ ์ฒ ํ์ "์ฌ์ฉ์๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ ํ ์คํธ ์ฝ๋๊ฐ ๋น์ทํ ์๋ก, ๋ ๋์ ์ ๋ขฐ๋๋ฅผ ์ป์ ์ ์๋ค"๋ ๊ฒ์ ๋๋ค. ์ด ์ฒ ํ์ ๊ธฐ๋ฐํ ์ฃผ์ ์์น์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
1. ์ฌ์ฉ์์ ํ๋์ ํ ์คํธํ๋ผ (Test User Behavior)
- ๋ฌด์์ ํ ๊ฒ์ธ๊ฐ? ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ํด๋ฆญํ๊ณ , ํผ์ ๋ด์ฉ์ ์ ๋ ฅํ๋ฉฐ, ํ๋ฉด์ ํน์ ํ ์คํธ๊ฐ ๋ํ๋๋์ง ํ์ธํ๋ ๊ฒ๊ณผ ๊ฐ์ด ์ฌ์ฉ์์ ๊ด์ ์์ ํ ์คํธ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
- ๋ฌด์์ ํผํ ๊ฒ์ธ๊ฐ? ์ปดํฌ๋ํธ์ ๋ด๋ถ ์ํ(์:
useState์ ๊ฐ)๋ ํน์ CSS ํด๋์ค ์ด๋ฆ, ๊ตฌํ ๋ํ ์ผ(div,span๋ฑ)์ ์์กดํ๋ ํ ์คํธ๋ ํผํด์ผ ํฉ๋๋ค. ์ด๋ฐ ํ ์คํธ๋ ๋ฆฌํฉํ ๋ง ์ ์ฝ๊ฒ ๊นจ์ง๋ฉฐ, ์ ์ง๋ณด์ ๋น์ฉ์ด ๋์์ง๋๋ค.
2. ๊ตฌํ ์ธ๋ถ ์ ๋ณด๋ ํ ์คํธํ์ง ๋ง๋ผ (Don't Test Implementation Details)
getByTestId๋ ์ตํ์ ์๋จ์ผ๋ก ์ฌ์ฉํ๊ณ , ์ฌ์ฉ์์๊ฒ ์๋ฏธ ์๋ ์ฟผ๋ฆฌ(ByRole,ByLabelText,ByText,ByPlaceholderText)๋ฅผ ์ฐ์ ์ ์ผ๋ก ์ฌ์ฉํ์ธ์. ์๋ฅผ ๋ค์ด,screen.getByRole('button', { name: /ํด๋ฆญ/i })์ฒ๋ผ ์ฌ์ฉ์์ ์ ๊ทผ์ฑ(accessibility)์ ๊ณ ๋ คํ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฐ์ค๋ฝ๊ฒ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ฉํ ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
3. ์๊ณ ์ง์ค๋ ํ ์คํธ๋ฅผ ์์ฑํ๋ผ (Write Small, Focused Tests)
- ํ๋์ ํ
์คํธ(
it๋ธ๋ก)๋ ํ๋์ ํน์ ๊ธฐ๋ฅ์ ๊ฒ์ฆํด์ผ ํฉ๋๋ค. ์ฌ๋ฌ ๊ธฐ๋ฅ์ ํ ํ ์คํธ์ ์์ง ๋ง์ธ์. describe๋ธ๋ก์ ํ์ฉํ์ฌ ๊ด๋ จ ํ ์คํธ๋ฅผ ๊ทธ๋ฃนํํ๊ณ , ๊ฐ ํ ์คํธ๋ ๋ช ํํ ํ๋์ ๋ชฉํ๋ฅผ ๊ฐ์ง๋๋ก ์์ฑํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํ ์คํธ ์คํจ ์ ์์ธ์ ํ์ ํ๊ธฐ ์ฝ๊ณ , ๋๋ฒ๊น ์๊ฐ์ ๋จ์ถํ ์ ์์ต๋๋ค.
4. ํ ์คํธ ์ฝ๋๋ฅผ ๊ฐ๋ ์ฑ ์๊ฒ ์์ฑํ๋ผ
- ํ ์คํธ ์ค๋ช ์ ๋ช ํํ๊ฒ ์์ฑํ์ฌ ํ ์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ํ๋์ ์ ์ ์๊ฒ ํ์ธ์.
- AAA ํจํด(Arrange-Act-Assert)์ ๋ฐ๋ฅด๋ ๊ฒ์ด ์ข์ต๋๋ค.
- Arrange: ํ
์คํธ์ ํ์ํ ํ๊ฒฝ ๋ฐ ๋ฐ์ดํฐ๋ฅผ ์ค๋นํฉ๋๋ค. (
render(), Mock ๋ฐ์ดํฐ ๋ฑ) - Act: ์ฌ์ฉ์์ ํ๋์ ์๋ฎฌ๋ ์ด์
ํฉ๋๋ค. (
fireEvent.click(),fireEvent.change(),userEvent.type()) - Assert: ๊ธฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์ฆํฉ๋๋ค. (
expect().toBeInTheDocument())
์ฝ๋ ํ์ง
๋ฆฌํฉํ ๋ง์ด ํ์ํ ๋ถ๋ถ (EventForm)
- ๋ฌธ์ ๋ง์ props ๊ฐ์์ ๋ณต์ก์ฑ
const defaultProps = {
events: mockEvents,
title: '',
setTitle: vi.fn(),
date: '',
setDate: vi.fn(),
startTime: '',
endTime: '',
description: '',
setDescription: vi.fn(),
location: '',
setLocation: vi.fn(),
category: '์
๋ฌด',
setCategory: vi.fn(),
isRepeating: false,
setIsRepeating: vi.fn(),
repeatType: 'none' as const,
repeatInterval: 1,
repeatEndDate: undefined,
notificationTime: 10,
setNotificationTime: vi.fn(),
startTimeError: null,
endTimeError: null,
editingEvent: null,
handleStartTimeChange: vi.fn(),
handleEndTimeChange: vi.fn(),
resetForm: vi.fn(),
onSaveEvent: vi.fn(),
onOverlapDetected: vi.fn(),
};
- ๊ทน์ ์ค๋ฌ์ด EventForm์ props๋ฅผ ๋ฆฌํฉํ ๋ง์์ ํด๊ฒฐ์ ๋ชปํ์ต๋๋ค..
- ์ํ๊ฐ, ์ํ ์ค์ ํจ์, ์๋ฌ ์ํ, ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ ๋ค์ํ ํ์ ํผ์ฌ๋์ด ์์ต๋๋ค.
- ๋น ๋ฌธ์์ด, null, vi.fn() ๋ฑ ํ ์คํธ์ฉ ๊ธฐ๋ณธ๊ฐ๋ค์ด ์์ฌ ์์ต๋๋ค.
- ํ ์คํธ๋ณ Props ์ค๋ฒ๋ผ์ด๋์ ๋ณต์ก์ฑ
it('์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ๊ฐ ์๋ ๊ฒฝ์ฐ ์๋ฌ ํ ์คํธ๊ฐ ํ์๋๋ค', async () => {
renderWithProvider(
<EventForm
{...defaultProps}
title="ํ
์คํธ ์ด๋ฒคํธ"
date="2025-01-20"
startTime="14:00"
endTime="13:00"
startTimeError="์์ ์๊ฐ์ ์ข
๋ฃ ์๊ฐ๋ณด๋ค ๋นจ๋ผ์ผ ํฉ๋๋ค."
endTimeError="์ข
๋ฃ ์๊ฐ์ ์์ ์๊ฐ๋ณด๋ค ๋ฆ์ด์ผ ํฉ๋๋ค."
/>
);
// ... ํ
์คํธ ๋ก์ง
});
- ํ ์คํธ๋ง๋ค props ์ค์ ์ด ๋ฌ๋ผ ๋ณต์กํจ์ด ์ฒด๊ฐ๋ฉ๋๋ค.
- props ๊ตฌ์กฐ ๋ณ๊ฒฝ ์ ์ํฅ ๋ฒ์
it('๋ชจ๋ ์ ๋ณด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์
๋ ฅ๋ ๊ฒฝ์ฐ ์ผ์ ์ด ์ ์ฅ๋๋ค', async () => {
const validProps = {
...defaultProps,
title: '์ ์ด๋ฒคํธ',
date: '2025-01-20',
startTime: '10:00',
endTime: '11:00',
description: 'ํ
์คํธ ์ค๋ช
',
location: 'ํ์์ค',
category: '์
๋ฌด',
notificationTime: 10,
};
renderWithProvider(<EventForm {...validProps} />);
// ... ํ
์คํธ ๋ก์ง
});
[๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ]
- EventForm ์ปดํฌ๋ํธ ์์ฒด๊ฐ ๋ง์ props๋ฅผ ๋ฐ๋๋ก ์ค๊ณ๋์ด ์์ต๋๋ค. ์ด๋ ์ปดํฌ๋ํธ๊ฐ ๋๋ฌด ๋ง์ ์ฑ ์์ ๊ฐ์ง๊ณ ์์์ ์๋ฏธํฉ๋๋ค.
- ๊น์ ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ props๋ฅผ ๊ณ์ ์ ๋ฌํ๋ ๊ตฌ์กฐ๋ก ์ค๊ณ๋์ด ์์ต๋๋ค.
- ๊ฐ ํ ์คํธ๋ง๋ค ํ์ํ props๋ง ์ค๋ฒ๋ผ์ด๋ํ๋ ๊ฒ์ด ๋ณต์กํจ์ ์ ๋ฐํฉ๋๋ค.
[๊ฐ์ ๋ฐฉํฅ]
- Props ํฉํ ๋ฆฌ ํจ์ ๋์
const createEventFormProps = (overrides: Partial<EventFormProps> = {}): EventFormProps => {
const baseProps: EventFormProps = {
events: [],
title: '',
setTitle: vi.fn(),
date: '',
setDate: vi.fn(),
startTime: '',
endTime: '',
description: '',
setDescription: vi.fn(),
location: '',
setLocation: vi.fn(),
category: '์
๋ฌด',
setCategory: vi.fn(),
isRepeating: false,
setIsRepeating: vi.fn(),
repeatType: 'none',
repeatInterval: 1,
repeatEndDate: undefined,
notificationTime: 10,
setNotificationTime: vi.fn(),
startTimeError: null,
endTimeError: null,
editingEvent: null,
handleStartTimeChange: vi.fn(),
handleEndTimeChange: vi.fn(),
resetForm: vi.fn(),
onSaveEvent: vi.fn(),
onOverlapDetected: vi.fn(),
};
return { ...baseProps, ...overrides };
};
- ์๋๋ฆฌ์ค๋ณ props ํฌํผ ํจ์
const createValidEventFormProps = (overrides: Partial<EventFormProps> = {}) => {
return createEventFormProps({
title: '์ ์ด๋ฒคํธ',
date: '2025-01-20',
startTime: '10:00',
endTime: '11:00',
description: 'ํ
์คํธ ์ค๋ช
',
location: 'ํ์์ค',
category: '์
๋ฌด',
notificationTime: 10,
...overrides,
});
};
const createErrorEventFormProps = (overrides: Partial<EventFormProps> = {}) => {
return createEventFormProps({
title: '',
date: '',
startTime: '14:00',
endTime: '13:00',
startTimeError: '์์ ์๊ฐ์ ์ข
๋ฃ ์๊ฐ๋ณด๋ค ๋นจ๋ผ์ผ ํฉ๋๋ค.',
endTimeError: '์ข
๋ฃ ์๊ฐ์ ์์ ์๊ฐ๋ณด๋ค ๋ฆ์ด์ผ ํฉ๋๋ค.',
...overrides,
});
};
const createEditModeEventFormProps = (editingEvent: Event, overrides: Partial<EventFormProps> = {}) => {
return createEventFormProps({
editingEvent,
title: '์์ ๋ ์ด๋ฒคํธ',
date: '2025-01-15',
startTime: '09:00',
endTime: '10:00',
...overrides,
});
};
[์์ ์ฝ๋]
describe('ํผ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ์ ์ฅ', () => {
it('ํ์ ์ ๋ณด๊ฐ ๋๋ฝ๋ ๊ฒฝ์ฐ ์๋ฌ ํ ์คํธ๊ฐ ํ์๋๋ค', async () => {
const props = createEventFormProps({
title: '',
date: '',
});
renderWithProvider(<EventForm {...props} />);
const saveButton = screen.getByTestId('event-submit-button');
fireEvent.click(saveButton);
expect(await screen.findByText('ํ์ ์ ๋ณด๋ฅผ ๋ชจ๋ ์
๋ ฅํด์ฃผ์ธ์.')).toBeInTheDocument();
expect(props.onSaveEvent).not.toHaveBeenCalled();
});
it('์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ๊ฐ ์๋ ๊ฒฝ์ฐ ์๋ฌ ํ ์คํธ๊ฐ ํ์๋๋ค', async () => {
const props = createErrorEventFormProps();
renderWithProvider(<EventForm {...props} />);
const saveButton = screen.getByTestId('event-submit-button');
fireEvent.click(saveButton);
expect(await screen.findByText('์๊ฐ ์ค์ ์ ํ์ธํด์ฃผ์ธ์.')).toBeInTheDocument();
expect(props.onSaveEvent).not.toHaveBeenCalled();
});
it('๋ชจ๋ ์ ๋ณด๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์
๋ ฅ๋ ๊ฒฝ์ฐ ์ผ์ ์ด ์ ์ฅ๋๋ค', async () => {
const props = createValidEventFormProps();
renderWithProvider(<EventForm {...props} />);
const saveButton = screen.getByTestId('event-submit-button');
fireEvent.click(saveButton);
await waitFor(() => {
expect(props.onSaveEvent).toHaveBeenCalledWith(
expect.objectContaining({
title: '์ ์ด๋ฒคํธ',
date: '2025-01-20',
startTime: '10:00',
endTime: '11:00',
})
);
});
});
});
๊ฐ์ ํจ๊ณผ์ ๊ธฐ๋ ๊ฒฐ๊ณผ
- ๊ฐ๋ ์ฑ ํฅ์
- ๊ฐ ํ ์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ํ๋์ ํ์ ๊ฐ๋ฅํด์ง๋๋ค.
- ๋ณต์กํ props ์ค๋ฒ๋ผ์ด๋ ๋ก์ง์ ๋ํด ํด์๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ํ ์คํธ์ ํต์ฌ ๊ฒ์ฆ ๋ก์ง์ด props ์ค์ ์ ๋ฌปํ์ง ์์ต๋๋ค.
- ์ ์ง๋ณด์์ฑ ํฅ์
- Props ๋ณ๊ฒฝ ์ํฅ์ ์ต์ํํ์ฌ ์๋ก์ด prop ์ถ๊ฐ ์ ํฉํ ๋ฆฌ ํจ์๋ง ์์
- ํ ์คํธ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ๋ถ์ฌํ์ฌ ๋ชจ๋ ํ ์คํธ์์ ๋์ผํ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
- ์๋๋ฆฌ์ค๋ณ props ํฌํผ ํจ์๋ก ํ ์คํธ ์ฝ๋ ์ค๋ณต ์ ๊ฑฐํ ์ ์์ต๋๋ค.
- ํ์ฅ์ฑ ํฅ์
- ์๋ก์ด ํ ์คํธ ์๋๋ฆฌ์ค ์ถ๊ฐ๊ฐ ์ฉ์ดํฉ๋๋ค. (ํฌํผ ํจ์๋ง ์ถ๊ฐ ํ๋ฉด ๋๋ค)
- ํฉํ ๋ฆฌ ํจ์ ์์ ์ผ๋ก ๋ชจ๋ ํ ์คํธ์ ๋ฐ์(Props ๊ตฌ์กฐ ๋ณ๊ฒฝ ๋์)
ํ์ต ํจ๊ณผ ๋ถ์
- ์์ง์ "์ด๋ฐ๊ฑฐ๊ตฌ๋" ๋ผ๊ธฐ ๋ณด๋ค๋ "์ด๋ฐ..๊ฑธ๊น..?" ์ ๋๋์ด ๊ฐํ ์๊ฐ๋ค์ด์์ต๋๋ค.
- ๋ค์์ฃผ ๊ณผ์ ๋ฅผ ์์ํ๊ธฐ ์ ์ ๊ณต์๋ฌธ์๋ฅผ ์ฝ๊ณ ์ ๋ฆฌํด๋ณด๋ ์๊ฐ์ ๊ฐ์ง๋ค๋ฉด ๋ค์ ๊ณผ์ ๋ฅผ ๋ ์ ์ํํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
๊ณผ์ ํผ๋๋ฐฑ
- ํ ์คํธ ์ฝ๋ ์์ฑ์ ๋ํด ๋์ด๋๋ณ๋ก ์ฒด๊ณ์ ์ผ๋ก ์ดํดํ ์ ์๋๋ก ๊ตฌ์ฑ๋ ๊ณผ์ ๋ผ๊ณ ๋๊ปด์ก์ต๋๋ค.
- ๋ค๋ง pass ๋์ด๋๊ฐ ๋๋ค๊ณ ๋๊ปด์ก์ต๋๋ค.ใ ใ
๋ฆฌ๋ทฐ ๋ฐ๊ณ ์ถ์ ๋ด์ฉ
- ์ ๊ฐ ์์ฑํ ๋จ์ ํ ์คํธ์ ์ ๋ขฐ๋๊ฐ ๋๋ค๋ ๊ฒ์ ์ค์ค๋ก ํ๋จํ๋ ์งํ๋ฅผ ์ธ์ด๋ค๋ฉด ์ด๋ค๊ฒ์ ๊ธฐ์ค์ผ๋ก ์๊ฐํด๋ณผ ์ ์์๊น์?
- ๋ํ ์ ๋ขฐ๋ ์๋ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ํด์๋ ์ด๋ค๊ฒ์ ๊ณ ๋ คํ๋ฉด์ ์์ฑํ๋ฉด ์ข์๊น์?
- QnA ์๊ฐ์ ์ ์๋ฏธํ ๋จ์ํ ์คํธ์ ๊ทธ๋ ์ง ๋ชปํ ๋จ์ํ ์คํธ์ ๋ํด ์ค๋ช ํด์ฃผ์ จ๋๋ฐ, ์ฌ์ค ๊ตฌ๋ณ์ ์ ๋ชปํ๊ฒ ์ต๋๋ค. ๋จ์ ํ ์คํธ ์์ฑ์ ์ต์ํด์ง๋ค๋ฉด ๊ตฌ๋ณํ๊ธฐ ์ด๋ ต์ง ์์ ๊ฒ์ผ๊น์? ์ด์ฌ์๊ฐ ์ด๋ค ๊ฒ์ ์์ฃผ๋ก ๊ณต๋ถํ๋ฉด ๋น ๋ฅด๊ฒ ๊ฐ๊ฐ์ ์ตํ ์ ์์๊น์?
๊ณผ์ ํผ๋๋ฐฑ
์โฆโฆ ํ๊ณ ๋ด์ฉ์ด ๋ฌด์จ ๋ ผ๋ฌธ์ด๋ค์ :) ์งํธ๋ ๋ฉ์ง๋๋ค.
Q. ์ ๊ฐ ์์ฑํ ๋จ์ ํ ์คํธ์ ์ ๋ขฐ๋๊ฐ ๋๋ค๋ ๊ฒ์ ์ค์ค๋ก ํ๋จํ๋ ์งํ๋ฅผ ์ธ์ด๋ค๋ฉด ์ด๋ค๊ฒ์ ๊ธฐ์ค์ผ๋ก ์๊ฐํด๋ณผ ์ ์์๊น์?
A. ์ฌ์ค ๊ทธ๊ฒ์ ํ๋จํ ์ ์๋ ์งํ๋ ์๋ค๊ณ ์๊ฐํด์. ์ ์ฑ์ ์ธ ๊ฒ๋ง๊ณ ์ ๋์ ์ผ๋ก๋ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋์์ด ๋ ์ ๋ ์์ ๊ฒ ๊ฐ์์. ๊ธฐ๋ณธ์ ์ผ๋ก ํ ์คํธ๋ฅผ ๊ธฐ์ค(ํ์์ ์ ํ ์ ๋ต)์ ๋ง๊ฒ ์ ์์ฑํ๋ค๋ ๊ฐ์ ํ์ ์ผ๋ง๋ ์ปค๋ฒํ๊ณ ์๋์ง๋ฅผ ์ ์ ์์ผ๋๊น์. ์ ์ฒด์ ์ธ ์ปค๋ฒ๋ฆฌ์ง๋ณด๋ค๋ ๋ธ๋์น ์ปค๋ฒ๋ฆฌ์ง๋ก ์ฝ๋์ ์ด๋๋ถ๋ถ์ด ํ ์คํธ๋ฅผ ํตํด ์คํ๋์ง ์๋์ง๋ฅผ ํ์ธํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. ์ ์ฑ์ ์ธ ์งํ๋ ์๋ง ์ฐพ๊ธฐ ํ๋ค ๊ฒ ๊ฐ์์. ์ผ๋จ ํ ์คํธ๋ โ๊นจ์ ธ์ผ ํ ๋ ์ ๊นจ์ ธ์ฃผ๋ฉดโ ์ ๋ขฐ๋๊ฐ ๋๋ค๊ณ ๋ณผ ์ ์๊ณ "๊นจ์ ธ์ผ ํ๋๋ฐ ์๊นจ์ง๋ฉด" ์ ๋ขฐ๋๊ฐ ๋ฎ๋ค๊ณ ๋ณผ ์ ์์ ๊ฒ ๊ฐ์์. "์๊นจ์ ธ์ผ ํ๋๋ฐ ๊นจ์ ธ๋" ๋ง์ฐฎ๊ฐ์ง๊ฒ ์ฃต.
Q. ์ ๋ขฐ๋ ์๋ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ํด์๋ ์ด๋ค๊ฒ์ ๊ณ ๋ คํ๋ฉด์ ์์ฑํ๋ฉด ์ข์๊น์?
์ ๋ขฐ๋๋ฅผ ๋์ด๋ ๋จ ํ๊ฐ์ง๋ ํ ์คํธ๋ ๋ชจ๋๋ก ์๊ฐํ๊ณ ํ ์คํธ ๋์ ๋ชจ๋๊ณผ ํจ๊ป ๊ณ์ ์ ์ง๋ณด์ํ๊ณ ์ค๋ณต์ฝ๋๋ฅผ ์ ๊ฑฐํ๊ณ ํ์ ์์ด์ง ์ฝ๋๋ ์์ ๋ ์์ ์ ํ๋ ๊ฒ ๋ฐ์๋ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
Q. QnA ์๊ฐ์ ์ ์๋ฏธํ ๋จ์ํ ์คํธ์ ๊ทธ๋ ์ง ๋ชปํ ๋จ์ํ ์คํธ์ ๋ํด ์ค๋ช ํด์ฃผ์ จ๋๋ฐ, ์ฌ์ค ๊ตฌ๋ณ์ ์ ๋ชปํ๊ฒ ์ต๋๋ค. ๋จ์ ํ ์คํธ ์์ฑ์ ์ต์ํด์ง๋ค๋ฉด ๊ตฌ๋ณํ๊ธฐ ์ด๋ ต์ง ์์ ๊ฒ์ผ๊น์?โจ์ด์ฌ์๊ฐ ์ด๋ค ๊ฒ์ ์์ฃผ๋ก ๊ณต๋ถํ๋ฉด ๋น ๋ฅด๊ฒ ๊ฐ๊ฐ์ ์ตํ ์ ์์๊น์?
A. ์ผ๋จ ๋ง์ด ์์ฑํ๋ ์๋ฐ์๋ ์๋ ๊ฒ ๊ฐ์ต๋๋ค! ๊ด๋ จ ์ฑ ์ ์ฐพ์์ ๋ณด๋ฉด ๋ ๋์์ด ๋ ๊ฒ ๊ฐ์์. ์ ๊ฐ ๊ฒฝํํด๋ณธ ๋ฐ๋ก๋ ํ 1๋ ์ ๋ ์์ฑํ๋ฉด ๊ฐ์ด ์๊ธฐ๊ณ ์ด๋ค ๊ฒ์ ํ ์คํธํ ๋๋ ์ด๋ค ๋ฐฉ๋ฒ์์ด๋ผ๋ ๋ ธํ์ฐ๊ฐ ์ถ์ ๋๋ ๊ฒ ๊ฐ์ต๋๋ค.