대부분의 개발은 프론트와 백엔드가 동시에 개발을 시작한다.
문제는 프론트가 백엔드 API가 있어야 연동을 시작할 수 있다는 것…
UI 먼저 만들게요~ -> API 나오면 붙일게요! -> ...API 아직임
-> 있어도 스펙 바뀜 or 서버 쥬금ㅠㅠ or 내가 필요한 에러 케이스 재현이 어려움
그래서 프론트는 연동을 못 해서 멈추거나, 임시로 const mockData = […] 같은 걸로 UI를 만든다.
mock data 만들어서 그냥 UI에 때려박으면 당연히 만들 수는 있다. 근데 시간이 지나면 문제점이 생긴다.
실제 API 붙일 때 또 수정
로딩 처리
에러 처리
refetch 타이밍, staleTime 등등
⇒ UI는 만들었는데 데이터 패칭 구조를 다시 구현해야함
const mockUsers = [
{ id: 1, name: "연희" },
{ id: 2, name: "혁수" },
];
export function UserList() {
return (
<ul>
{mockUsers.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
// API 연동하면서 로직 추가
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
async function fetchUsers() {
const res = await axios.get("/api/users");
return res.data;
}
export function UserList() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
retry: 2,
});
if (isLoading) return <div>로딩중...</div>;
if (error)
return (
<div>
에러 발생 <button onClick={() => refetch()}>다시 시도</button>
</div>
);
return (
<ul>
{data.map((u: any) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
테스트/QA에서 재현이 어려움
API Mocking은 “진짜 서버 대신, 서버가 줄 것 같은 응답(API 명세서 기반)을 가짜로 만들어서 API 연동 흐름을 그대로 개발하는 것”
단순히 데이터를 넣는 것이 아니라, GET /api/users 요청을 보내면 → 서버가 있는 것처럼 로딩도 있고, 실패도 가능하고, 지연도 넣을 수 있고, 응답 형태도 실제 API와 맞춰서 내려줌.
즉, 프론트는 서버가 없어도 연동 개발을 할 수 있음!!!!!!
요청을 어디서 낚아채는지에 따라서 분류됨.
사실 프로젝트 끝나고 서버 꺼버리면 현실적으로 프론트는 코드 개선하기가 쉽지가 않다..ㅠㅠ 하지만 MSW를 작성해두면, mock data로 UI 확인할 수 있기 때문에 UI나 코드 개선 가능!
사용 전
사용 후
브라우저에서 MSW를 하나 등록한다. 이 SW는 브라우저 안에서 “프록시”처럼 동작하면서 페이지에서 나가는 네트워크 요청을 가로챌 수 있음
흐름:
axios.get(”/api/users”) 호출pnpm add msw
npx msw init public /--save
이거 하면 public/mockServiceWorker.js가 생김. 이 파일이 있어야 브라우저에서 SW가 뜸.
// 폴더구조
src/
mocks/
handlers.ts
browser.ts
main.tsx
//src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
async function enableMocking() {
if (import.meta.env.MODE !== "development") return;
const { worker } = await import("./mocks/browser");
await worker.start({
onUnhandledRequest: "warn", // 핸들러 없을 때 처리
});
}
const queryClient = new QueryClient();
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
});
// src/mocks/handlers.ts
import { http, HttpResponse, delay } from "msw";
export const handlers = [
http.get("/api/users", async () => {
await delay(400); // 로딩 UX 보이게 일부러 지연
return HttpResponse.json([
{ id: 1, name: "연희" },
{ id: 2, name: "혁수" },
]);
}),
];
실제로 API 호출하는 컴포넌트
// src/App.tsx
import { useQuery } from "@tanstack/react-query";
type User = { id: number; name: string };
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch users");
return res.json();
}
export default function App() {
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
retry: 1,
});
if (isLoading) return <div>로딩중...</div>;
if (isError)
return (
<div>
에러: {(error as Error).message}
<button onClick={() => refetch()}>재시도</button>
</div>
);
return (
<div>
<h1>Users</h1>
<ul>
{data!.map((u) => (
<li key={u.id}>
{u.id}. {u.name}
</li>
))}
</ul>
</div>
);
}