๐Ÿ“ก 07. [์‹ค๋ฌด] TanStack Query ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ(Testing) ์ „๋žต

2026๋…„ 3์›” 4์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

React Query ํ™˜๊ฒฝ์— ํŠนํ™”๋œ ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ๊ตฌ์ถ•๋ฒ•, QueryClient ๊ฒฉ๋ฆฌ ํŒจํ„ด, ๊ทธ๋ฆฌ๊ณ  MSW๋ฅผ ๊ฒฐํ•ฉํ•œ ์™„๋ฒฝํ•œ ๋„คํŠธ์›Œํฌ ๋ชจํ‚น(Mocking) ์ „๋žต์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ฒ  ๋‹˜, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋Œ๋ ธ๋”๋‹ˆ 'Network Error: Fetch is not defined' ๋งŒ 50์ค„ ๋œจ๋Š”๋ฐ, Jest ์•ˆ์—์„œ ์ง„์งœ ๋ฐฐํฌ ์„œ๋ฒ„๋ฅผ ์ฐŒ๋ฅด๊ณ  ๊ณ„์…จ๋‚˜์š”?"

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "ํ…Œ์ŠคํŠธ๋งŒ ์งœ๋ฉด ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๊ฐ€ ๋‹ค ๋ฑ‰์–ด๋‚ด์š”!"

(๊ธˆ์š”์ผ ์ €๋…, ๋งˆ์ง€๋ง‰ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์ฑ™๊ธฐ๋ ค๋‹ค ์˜ค์—ดํ•˜๋Š” ์˜์ฒ )

๐Ÿฃ ์˜์ฒ : ์•„๋‹ˆ ๋ฆฌ๋“œ ๋‹˜! ์ด์ œ ์™„๋ฒฝํ•˜๊ฒŒ ์ปดํฌ๋„ŒํŠธ ๋‹ค ์งฐ์œผ๋‹ˆ๊นŒ ๋“ ๋“ ํ•˜๊ฒŒ Vitest(๋˜๋Š” Jest) ๋ž‘ React Testing Library(RTL) ์ผœ์„œ ํ…Œ์ŠคํŠธ ๋ ˆ์ด์–ด ์–น์œผ๋ ค๊ณ  ํ–ˆ๊ฑฐ๋“ ์š”?
๊ทผ๋ฐ ํ…Œ์ŠคํŠธ(npm run test) ์‹คํ–‰ํ•˜์ž๋งˆ์ž... ์—๋Ÿฌ๊ฐ€ ์‚ฐ๋”๋ฏธ์ฒ˜๋Ÿผ ์Ÿ์•„์ ธ์š”!
No QueryClient set, use QueryClientProvider ์—๋Ÿฌ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ,
๊ธฐ๊ป Provider ๊ฐ์‹ธ์คฌ๋”๋‹ˆ ์ด๋ฒˆ์—” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์•ˆ ์ผœ์ ธ ์žˆ๋‹ค๊ณ  Network Timeout ๋‚˜๊ณ ์š”,
๊ทธ๊ฑฐ ๊ณ ์ณค๋”๋‹ˆ "1๋ฒˆ ํ…Œ์ŠคํŠธ์—์„œ ๋งŒ๋“  ์บ์‹œ ๋ฐ์ดํ„ฐ" ๊ฐ€ "2๋ฒˆ ํ…Œ์ŠคํŠธ" ์— ์˜ค์—ผ๋ผ์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•‘ํ์œผ๋กœ ๊นจ์ ธ์š”. ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ํ…Œ์ŠคํŠธ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•œ ์˜์—ญ์ธ๊ฐ€์š”?

๐Ÿฆ ์˜ํ˜ธ: ์˜์ฒ  ๋‹˜, ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์„ธ๊ณ„์— ๋ฐœ์„ ๋“ค์ด์…จ๊ตฐ์š”. useQuery๊ฐ€ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์งฑ๋ฐ•ํ˜€ ์žˆ๋Š”๋ฐ ๊ทธ๊ฑธ ๋กœ์ปฌ ๋…ธ๋“œ(Node.js / JSDOM) ํ™˜๊ฒฝ์—์„œ ๊ทธ๋ƒฅ ๋ Œ๋”๋งํ•˜๋ ค๊ณ  ํ•˜๋‹ˆ๊นŒ ์˜จ๊ฐ– ์˜ค๋ฅ˜๋ž€ ์˜ค๋ฅ˜๋Š” ๋‹ค ํ„ฐ์ง€๋Š” ๊ฒ๋‹ˆ๋‹ค.

  1. QueryClientProvider ์—†์ด๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (Provider ๋ž˜ํ•‘ ์œ„์ž„์„ ๋ชจ๋ฅด์‹ฌ)
  2. ํ…Œ์ŠคํŠธ ์ค‘์— ์ง„์งœ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์— ํŠธ๋ž˜ํ”ฝ์„ ์ฐŒ๋ฅด๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ๋น ๋ฅด๊ณ  ๊ฒฉ๋ฆฌ๋œ ๋ง(Mock) ์„ ์จ์•ผ์ฃ . MSW(Mock Service Worker)๋ฅผ ๋„์ž…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (TkDodo #5).

5๋…„ ์ฐจ ์‹ค๋ฌด ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ์˜ ์„ฑ๋ฐฐ, "MSW + 1 ํ…Œ์ŠคํŠธ 1 QueryClient ํŒฉํ† ๋ฆฌ" ์„ธํŒ…์„ ํŒŒํ—ค์ณ ๋ด…์‹œ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ์™€ ๋„คํŠธ์›Œํฌ ๊ฐ€๋กœ์ฑ„๊ธฐ

๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ์ƒํƒœ ๋ณ€ํ•˜๋Š” ๊ฑด ๋ˆ„๊ตฌ๋‚˜ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ "๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐฑ์—”๋“œ API๋ฅผ ์ฝœํ•ด์„œ ํ”„๋กœํ•„์„ ๊ทธ๋ ค์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ"๋ฅผ ๋ธŒ๋ผ์šฐ์ €๋„ ์—†๋Š” CI/CD ์ฝ˜์†”์ฐฝ(Terminal) ์•ˆ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ๋‚œ์ด๋„๊ฐ€ ๊ทน์ƒ์œผ๋กœ ๋œ๋‹ˆ๋‹ค.

React Query ํ™˜๊ฒฝ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์šด ์ด์œ ๋Š” ๊ทธ ๊ฐ•๋ ฅํ•œ ์žฅ์  ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

  • ์บ์‹ฑ(Caching)์˜ ๋ถ€์ž‘์šฉ: ํ…Œ์ŠคํŠธ A์—์„œ ์ƒ์„ฑ๋œ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€, ํ…Œ์ŠคํŠธ B๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๊นŒ์ง€ ๋ฉ”๋ชจ๋ฆฌ(QueryCache) ์— ์ƒํ•ด(Stale) ์žˆ์ง€ ์•Š์•„์„œ ๋‚จ์•„๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. (์ƒํƒœ ์˜ค์—ผ)
  • ๋น„๋™๊ธฐ ์žฌ์‹œ๋„(Retry): ํ…Œ์ŠคํŠธ ์„œ๋ฒ„๊ฐ€ ์—†์–ด์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด, ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๊ฐ€ ๋ฌด๋ ค 3๋ฒˆ์ด๋‚˜ ์žฌ์‹œ๋„๋ฅผ ํ•˜๋А๋ผ ํ…Œ์ŠคํŠธ ํ•˜๋‚˜๋‹น ์‹œ๊ฐ„์ด ๋ฌดํ•œ์ • ์ง€์—ฐ๋ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์ œ์••ํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ์˜ต์…˜์„ ๋ชจ๋ฅด๋ฉด, ํ‰์ƒ E2E(Cypress) ํ…Œ์ŠคํŠธ์—๋งŒ ์˜์กดํ•˜๋ฉฐ ์—‰์„ฑํ•œ ์•ฑ์„ ๋งŒ๋“ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


1. ์ „์—ญ ์˜ค์—ผ ์ฐจ๋‹จ: QueryClient ์ธ์Šคํ„ด์Šค ๊ฒฉ๋ฆฌ ํ…Œํฌ๋‹‰

์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์ €์ง€๋ฅด๋Š” ์‹ค์ˆ˜๊ฐ€ ๋ฐ”๋กœ "์šด์˜ ์ฝ”๋“œ์— ์žˆ๋Š” QueryClient๋ฅผ ๊ฐ€์ ธ์™€์„œ ํ…Œ์ŠคํŠธ์— ๋„ฃ๋Š” ์ง“" ์ž…๋‹ˆ๋‹ค.
์šด์˜ ์ฝ”๋“œ๋Š” ์ „์—ญ ๋ณ€์ˆ˜ ํ•˜๋‚˜๋ฅผ ํ‰์ƒ ์šธ๊ถˆ๋จน๊ฒŒ ์บ์‹ฑ(staleTime: 5๋ถ„) ์„ธํŒ…์„ ํ•ด๋’€๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ ์นจ๋ฒ” ๋ฒ„๊ทธ๊ฐ€ ์ž‘๋ ฌํ•ฉ๋‹ˆ๋‹ค.

โœ… ํ•ด๊ฒฐ์ฑ…: ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ๋Œ ๋•Œ๋งˆ๋‹ค(๋งค it ์ด๋‚˜ test ๋ธ”๋ก๋งˆ๋‹ค) ์ƒˆ์‚ฅ QueryClient๋ฅผ ์ฐ์–ด๋‚ด๋Š” ๋ž˜ํผ(Wrapper) ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ ๋‹ค!

// ๐Ÿ“ tests/utils.tsx (ํ…Œ์ŠคํŠธ ํ—ฌํผ ํŒŒ์ผ)
import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 
// ๐Ÿš€ ์ปค์Šคํ…€ ๋ Œ๋”๋Ÿฌ ์ƒ์„ฑ!
export function renderWithQueryClient(ui: React.ReactElement) {
  // ํ•ต์‹ฌ: ํ•จ์ˆ˜๊ฐ€ ๋ถˆ๋ฆด ๋•Œ๋งˆ๋‹ค ์™„์ „ํžˆ ์ƒˆ๋กญ๊ณ  ํ…… ๋นˆ ์ฟผ๋ฆฌ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  const testQueryClient = new QueryClient({
    defaultOptions: {
      queries: {
        // ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์šฉ ์„ธํŒ…์˜ ๊ตญ๋ฃฐ: ์žฌ์‹œ๋„ ๋ฐœ์•…์„ ๊บผ๋ฒ„๋ฆฐ๋‹ค! ์—๋Ÿฌ ๋‚˜๋ฉด ์ฆ‰์‹œ ์‹คํŒจ ๋œจ๊ฒŒ.
        retry: false, 
        // ์˜ต์…˜: ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ์„ ์œ„ํ•ด ์บ์‹œ ์ฒญ์†Œ๋ฅผ ์ดˆ์Šคํ”ผ๋“œ๋กœ ๋Œ๋ฆฝ๋‹ˆ๋‹ค.
        gcTime: 0,
      },
    },
  });
 
  return {
    ...render(
      <QueryClientProvider client={testQueryClient}>
        {ui}
      </QueryClientProvider>
    ),
    // ๋‚˜์ค‘์— ํ…Œ์ŠคํŠธ ์•ˆ์ชฝ์—์„œ ์บ์‹œ๋ฅผ ์—ฟ๋ด์•ผ ํ•  ๋•Œ๋ฅผ ์œ„ํ•ด ์ธ์Šคํ„ด์Šค ๋ฐ˜ํ™˜
    testQueryClient,
  };
}

์ด์ œ ์‹ค์ œ ํ…Œ์ŠคํŠธ(*.test.tsx) ํŒŒ์ผ์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์”๋‹ˆ๋‹ค.

test('์œ ์ € ํ”„๋กœํ•„์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ Œ๋”๋ง ๋˜๋Š”๊ฐ€?', async () => {
  // ๋ฐฉ๊ธˆ ๋งŒ๋“  ํ—ฌํผ๋กœ ๋ธŒ๋ผ์šฐ์ € ์—†์ด ์ปดํฌ๋„ŒํŠธ ๊ฐ€์ƒ ๋งˆ์šดํŠธ ์™„์„ฑ!
  renderWithQueryClient(<UserProfile id={1} />);
  
  // ์š”์†Œ๊ฐ€ ํŒŒ๋ฐ”๋ฐ•- ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
  expect(await screen.findByText('์˜์ฒ ')).toBeInTheDocument();
});

2. API ๋ชจํ‚น(Mocking)์˜ ์ •์ : MSW (Mock Service Worker)

์ž, ๊ทธ๋Ÿผ ์ € UserProfile ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์žˆ๋Š” useQuery(fetchUser) ๋Š” ๋„๋Œ€์ฒด ์–ด๋””๋กœ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋‚ ๋ฆด๊นŒ์š”? JSDOM ํ™˜๊ฒฝ์—์„œ๋Š” ๊ฐ€๊ณ  ์‹ถ์–ด๋„ ๊ฐˆ ์„œ๋ฒ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!

์ด๋Ÿด ๋•Œ jest.mock() ์œผ๋กœ axios ํ†ต์‹ ๋ถ€๋ฅผ ๊ฑฐ๋ถํ•˜๊ฒŒ ๊ฐ€์งœ ๊ฐ์ฒด๋กœ ๊ฐˆ์•„๋ผ์šฐ๋Š” ๊ฑด ๋„ˆ๋ฌด ๊ณ ํ†ต์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค.
TanStack Query ์ œ์ž‘์ž๊ฐ€ ์ œ์ผ ๋งน๋ ฌํžˆ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ๋ฒ•์€, ์•„์˜ˆ ์„œ๋ฒ„(๋„คํŠธ์›Œํฌ ๊ณ„์ธต) ์ž์ฒด๋ฅผ ๊ฐ€๋กœ์ฑ„ ๋ฒ„๋ฆฌ๋Š” MSW ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ถ™์ด๋Š” ๊ฒ๋‹ˆ๋‹ค.

MSW๋ฅผ ์„ธํŒ…ํ•˜๋ฉด fetch ๋‚˜ axios ์ฝ”๋“œ๋ฅผ 1๋ฐ”์ดํŠธ๋„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์€ ์ฑ„, ๋‚ด๊ฐ€ ์œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์ง„์งœ ๋ฐฑ์—”๋“œ๊ฐ€ ์•„๋‹Œ ๋‚ด ๋กœ์ปฌ ํ…Œ์ŠคํŠธ๊ฐ€๋กœ์ฑ„๊ธฐ ๋ง์œผ๋กœ ์™์™ ๋นจ๋ ค ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.

// ๐Ÿ“ tests/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
 
export const handlers = [
  // "GET /api/users/1" ์š”์ฒญ์ด ๋‚ ๋ผ์˜ค๋ฉด, ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ 200 OK๋กœ ๋ฐ˜ํ™˜ํ•ด๋ž!
  http.get('https://my-api.com/users/1', () => {
    return HttpResponse.json({ name: '์˜์ฒ ', role: 'junior' })
  }),
 
  // ์˜ต์…˜: ์—๋Ÿฌ ์ƒํ™ฉ ์ปดํฌ๋„ŒํŠธ(Error Boundary) ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด 500 ์—๋Ÿฌ๋ฅผ ์˜๋Š” ๋ชฉ์—…๋„ ์ƒ์„ฑ
  http.get('https://my-api.com/users/999', () => {
    return new HttpResponse(null, { status: 500 })
  })
]

์ด์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ํ™˜๊ฒฝ ์…‹์—… ํŒŒ์ผ(setupTests.ts) ์—์„œ MSW ์„œ๋ฒ„๋ฅผ ์ผœ์ค๋‹ˆ๋‹ค.

import { setupServer } from 'msw/node'
import { handlers } from './handlers'
 
const server = setupServer(...handlers)
 
// ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „ ๋ชฉ์—… ์„œ๋ฒ„ ON
beforeAll(() => server.listen())
// ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ํ•ธ๋“ค๋Ÿฌ ๋ฆฌ์…‹ (๋…๋ฆฝ์„ฑ)
afterEach(() => server.resetHandlers())
// ๋ชฝ๋•… ๋๋‚˜๋ฉด ์„œ๋ฒ„ ๋”
afterAll(() => server.close())

์ด ๋‘ ๊ฐ€์ง€(QueryClient ๊ฒฉ๋ฆฌ + MSW ๊ฒฐํ•ฉ)๊ฐ€ ๋ญ‰์น˜๋Š” ์ˆœ๊ฐ„, ๋‹น์‹ ์€ ์–ด๋– ํ•œ ๋ณต์žกํ•œ useInfiniteQuery ๋‚˜ ๊ผฌ์ด๊ณ  ๊ผฌ์ธ ์บ์‹ฑ ์ƒํ™ฉ๋„ ๋ฐฑ์—”๋“œ ์—ฐ๊ฒฐ ์—†์ด ์™„๋ฒฝํ•˜๊ฒŒ ํ”„๋ก ํŠธ์—”๋“œ ๋‹จ๋…์œผ๋กœ ๋ธ”๋ž™๋ฐ•์Šค ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ์‹ ์˜ ๊ถŒ๋Šฅ์„ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค!


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์™€... ์˜ค๋Š˜ MSW ์„ธํŒ…ํ•ด์„œ ๋„คํŠธ์›Œํฌ ๊ฐ€๋กœ์ฑ„๋Š” ๊ฑฐ ๋ˆˆ์œผ๋กœ ๋ณด๋Š”๋ฐ ์†Œ๋ฆ„ ๋‹์•˜๋‹ค.
๋‚œ ๊ทธ๋™์•ˆ API ์ฝœํ•˜๋Š” ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ jest.spyOn().mockResolvedValue(...) ์ด๋Ÿฐ ์‹์œผ๋กœ ์–ด๊ฑฐ์ง€๋กœ ๊นŽ์•„์„œ ๋งŒ๋“ค์—ˆ์—ˆ๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ์ปดํฌ๋„ŒํŠธ ์•ˆ์ชฝ React Query์˜ ์บ์‹œ ๋กœ์ง์ด ์ง„์งœ๋กœ ์–ด๋–ป๊ฒŒ ์›€์ง์ด๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ๋ชป ํ–ˆ๋˜ ๊ฑฐ์˜€์–ด.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "React Query ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” 1) ๋ฌด์กฐ๊ฑด retry: false ์˜ต์…˜์„ ์ค€ ๊ป๋ฐ๊ธฐ 1ํšŒ์šฉ ๋นˆ QueryClient๋ฅผ ์ฐ์–ด๋‚ด์„œ ์ฃผ์ž…(Provider)ํ•ด ์ค„ ๊ฒƒ! 2) ๋„คํŠธ์›Œํฌ ์š”์ฒญ์€ ์ ˆ๋Œ€ mock ํ•จ์ˆ˜๋กœ ์น˜์ง€ ๋ง๊ณ  MSW๋ฅผ ๋ถ™์—ฌ ์„œ๋ฒ„์˜ ํ–‰์œ„๋ฅผ ์žฌํ˜„ํ•  ๊ฒƒ!"

์ด๋กœ์จ ๋ฆฌ๋“œ ๋‹˜์ด ์งœ์ฃผ์‹  17๊ฐœ์˜ ํ—˜๋‚œํ•œ TanStack Query ๊ธฐ์ดˆ/์‹ฌํ™” ๊ฐ€์ด๋“œ๋ผ์ธ ์—ฌ์ •์ด ๋ชฝ๋•… ๋‹ค ๋๋‚ฌ๋‹ค ใ… ใ… !
์ด์ œ ์ฒ˜์Œ ํ”„๋ก ํŠธ ์ž…์‚ฌํ–ˆ์„ ๋•Œ "queryKey๊ฐ€ ๋ญ์ฃ  ๋จน๋Š” ๊ฑด๊ฐ€์š”" ํ•˜๋˜ ๋‚˜ ์˜์ฒ ๋„, ์–ด๋”” ๊ฐ€์„œ ์ „์—ญ ์•„ํ‚คํ…์ฒ˜ ์บ์‹œ ๊ธˆ๊ณ  ๋”ฐ๊ณ  ๋‹ค๋‹ˆ๋Š” 5๋…„ ์ฐจ๋ผ๊ณ  ๋ช…ํ•จ ๋‚ด๋ฐ€ ์ˆ˜ ์žˆ๊ฒ ์ง€?! ์ˆ˜๊ณ ํ–ˆ๋‹ค ๋‚˜ ์ž์‹ !! ๐Ÿ”ฅ (์ฃผ๋ง์—” ํ‘น ์ž์•ผ์ง€...)


๐Ÿ“ ๋ฐฐ์šด ๋‚ด์šฉ ์ ๊ฒ€ํ•˜๊ธฐ (Quiz)

Q. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋˜ ์˜์ฒ ์ด๋Š” "์—๋Ÿฌ ๋‚ฌ์„ ๋•Œ Fallback ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž˜ ๊ทธ๋ ค์ฃผ๋Š”๊ฐ€?"(์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ) ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฌ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, Jest ์ฝ˜์†” ์ฐฝ์— ๋นจ๊ฐ„์ƒ‰ Timeout ์—๋Ÿฌ๊ฐ€ ๋œจ๋ฉด์„œ ๋ฌด๋ ค ํ…Œ์ŠคํŠธ ํ•˜๋‚˜๊ฐ€ ์™„๋ฃŒ๋˜๋Š” ๋ฐ 3์ดˆ ์ด์ƒ์ด ๋”œ๋ ˆ์ด๋˜๋Š” ํ˜„์ƒ์„ ๊ฒช์—ˆ์Šต๋‹ˆ๋‹ค. MSW๋Š” ์ •์ƒ์ ์œผ๋กœ 500 ์—๋Ÿฌ๋ฅผ ๋ฑ‰๊ฒŒ ์งœ ๋†จ๋Š”๋ฐ๋„ ๋ง์ด์ฃ . ์ด ์Šฌ๋กœ์šฐ ํ…Œ์ŠคํŠธ์˜ ๊ทผ๋ณธ์ ์ธ ์›์ธ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

โœ… ์ •๋‹ต: ํ…Œ์ŠคํŠธ๋ฅผ ๋ฎ์–ด์ฃผ๋Š” QueryClientProvider ๊ป๋ฐ๊ธฐ์— ํ…Œ์ŠคํŠธ ์ „์šฉ ์˜ต์…˜์ธ retry: false (์žฌ์‹œ๋„ ๋”) ์„ธํŒ…์„ ๊นœ๋นกํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: TkDodo ์•„ํ‹ฐํด #5์˜ ํ•ต์‹ฌ! React Query์˜ ์—„์ฒญ๋‚œ ๊ฐ•์ ์ธ "API ์š”์ฒญ ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ 3๋ฒˆ ์žฌ์‹œ๋„ (Exponential Backoff ์ง€์—ฐ์‹œ๊ฐ„ ํฌํ•จ)" ๊ธฐ๋Šฅ์€, ์•ผ์†ํ•˜๊ฒŒ๋„ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ(Node.js)์—์„œ๋„ ๋˜‘๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. MSW๊ฐ€ 500 ์—๋Ÿฌ๋ฅผ ์ฃผ๋ฉด, React Query๋Š” "์–ด? ์—๋Ÿฌ ๋‚ฌ์–ด? ์ž ๊น ๊ธฐ๋‹ค๋ ค๋ด. 1์ดˆ ๋’ค์— ๋‹ค์‹œ ์ฐ”๋Ÿฌ๋ณผ๊ฒŒ... ๋˜ ์—๋Ÿฌ ๋‚˜๋„ค? 2์ดˆ ๋’ค์— ๋‹ค์‹œ..." ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ ์ฐฝ์„ ํ•˜์—ผ์—†์ด ๋ฉˆ์ถฐ ์„ธ์›๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ 3๋ฒˆ ๋ฐœ์•…์„ ๋‹ค ๋๋‚ด๊ณ  ์™„์ „ํžˆ ์žฅ๋ ฌํžˆ ์ „์‚ฌํ–ˆ์„ ๋•Œ์—์•ผ ์ปดํฌ๋„ŒํŠธ ์ชฝ์— ์—๋Ÿฌ ๊ฒฐ๊ณผ๊ฐ’์„ ๋„˜๊ฒจ์ฃผ๋ฉฐ 3์ดˆ๋ฅผ ํ—ˆ๋น„ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜, ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋А๋ฆฐ ๊ฒŒ ์•„๋‹™๋‹ˆ๋‹ค! React Query๊ฐ€ ์‚ด๋ ค๋ณด๋ ค๊ณ  ๋’ค์—์„œ ์‹์€๋•€ ํ˜๋ฆฌ๋ฉด์„œ ์žฌ์‹œ๋„(Retry) ํ•˜๊ณ  ์žˆ๋Š” ๊ฑฐ์˜ˆ์š”. ํ…Œ์ŠคํŠธ ๋ Œ๋” ํ—ฌํผ์˜ ์ฟผ๋ฆฌ ์˜ต์…˜์— ๋ฌด์กฐ๊ฑด retry: false ํ•œ ์ค„์„ ์ˆ˜ํ˜ˆํ•ด์„œ 1ํŠธ๋งŒ์— ์ฆ‰์‚ฌ(?)ํ•˜๋„๋ก ๋งŒ๋“œ์…”์•ผ ์ˆจ ๋ง‰ํžˆ๋Š” ํ…Œ์ŠคํŠธ ๋ณ‘๋ชฉ ์ง€์—ฐ์„ ๋ฐ•์‚ด ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์˜ QueryClient๋Š” ๋ฌด์กฐ๊ฑด ์ž๋น„ ์—†๋Š” ๋…ธ๊ฐ€๋‹ค ๋ฐฉ์ง€, retry: false!