์ง์ง API๋ฅผ ๊ทธ๋ง ๋๋ฆฌ์ โ MSW๋ก ๋คํธ์ํฌ๋ฅผ ๊ฐ๋ก์ฑ ๊ฐ๋ฐํ๊ธฐ
YouTube Playlist Generator๋ฅผ ๋ง๋ค๋ฉฐ ์ ์ผ ํฌ๊ฒ ๋ฐ์ธ ๊ฑด API ํ ๋น๋(quota) ์ด์๋ค. YouTube Data API๋ ๊ฒ์ ํ ๋ฒ์ด 100 units์ด๊ณ ํ๋ฃจ ํ๋๊ฐ 10,000์ด๋ผ, ๊ฐ๋ฐํ๋ฉฐ ์๋ก๊ณ ์นจ ๋ช ๋ฒ ํ๋ฉด ํ๋ฃจ์น ํ ๋น๋์ด ์์๊ฐ์ ์ฆ๋ฐํ๋ค. "์ฝ๋ ํ ์ค ๊ณ ์น๊ณ ํ์ธ"์ ๋ฐ๋ณตํด์ผ ํ๋ ๊ฐ๋ฐ ๋จ๊ณ์์ ์ด๊ฑด ์น๋ช ์ ์ด๋ค.
๊ทธ๋์ ๊ฐ๋ฐ ๋จ๊ณ์์ ๋์ ํ๋ ๊ฒ MSW(Mock Service Worker) ๋ค. ๊ธฐ๋ฅ์ด ์์ ํ๋ ๋ค์๋ ๋นํ์ฑํํด์ ์ง๊ธ ์ฝ๋์๋ ์ฃผ์์ผ๋ก๋ง ํ์ ์ด ๋จ์ ์์ง๋ง, ์ด ๊ธ์ "์ MSW์๊ณ , ์ด๋ป๊ฒ ๋์ํ๋๊ฐ", ๊ทธ๋ฆฌ๊ณ ๋น์ ๋ด ์ค์ ์ด ์ด๋๊น์ง ๋๊ณ ์ด๋์ ์ด์คํ๋์ง๋ฅผ ์ ๋ฆฌํ๋ค.
ํํ ๋ชฉํน์ ํ๊ณ
๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ ๋ฐฉ๋ฒ์ ์๋๋ ์์๋ค.
axios๋ฅผ ๊ฐ์ผ ํจ์๋ฅผ ํต์งธ๋ก ๊ฐ์๋ผ์ฐ๊ธฐ- ์ปดํฌ๋ํธ์
if (mock) return dummy๊ฐ์ ๋ถ๊ธฐ ์ฌ๊ธฐ
๋ฌธ์ ๋ ์ฑ ์ฝ๋๋ฅผ ๊ฑด๋๋ ค์ผ ํ๋ค๋ ๊ฒ. ๋ชฉํน์ฉ ์ฝ๋๊ฐ ์ค์ ๋ก์ง์ ์์ด๊ณ , ๋ชฉ์ ๋๊ณ ์ผค ๋๋ง๋ค ์ฝ๋๋ฅผ ๊ณ ์ณ์ผ ํ๋ค. ์ค์๋ก ๋ชฉํน ์ฝ๋๋ฅผ ๋ฐฐํฌํ ์ํ๋ ์๋ค.
MSW์ ์์ด๋์ด: ๋คํธ์ํฌ ๊ณ์ธต์์ ๊ฐ๋ก์ฑ๊ธฐ
MSW๋ ์ ๊ทผ์ด ๋ค๋ฅด๋ค. Service Worker๋ก ๋ธ๋ผ์ฐ์ ์ ๋คํธ์ํฌ ์์ฒญ ์์ฒด๋ฅผ
๊ฐ๋ก์ฑ๋ค. ์ฑ์ ํ์์ฒ๋ผ fetch/axios๋ก ์ง์ง URL์ ์์ฒญ์ ๋ณด๋ด๊ณ , ๊ทธ
์์ฒญ์ด ๋คํธ์ํฌ๋ก ๋๊ฐ๊ธฐ ์ง์ ์ MSW๊ฐ ๋์์ฑ ๊ฐ์ง ์๋ต์ ๋๋ ค์ค๋ค.
์ฑ ์ฝ๋ MSW(Service Worker) ์ง์ง ์๋ฒ
โ axios.get('/search') โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโถโ (ํธ๋ค๋ฌ์ ๋งค์นญ๋จ) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโค ๊ฐ์ง JSON ์๋ต โ
โ โ (์ฌ๊ธฐ์ ๋, ์๋ฒ์ ์ ๊ฐ)
ํต์ฌ์ ์ฑ ์ฝ๋๊ฐ ๋ชฉํน์ ์กด์ฌ๋ฅผ ์ ํ ๋ชจ๋ฅธ๋ค๋ ์ ์ด๋ค. axios.get('/search')
๊ทธ๋๋ก ๋๋ฉด ๋๋ค. ๋ชฉ์ ๋๋ฉด ๊ทธ ์์ฒญ์ด ์ง์ง ์๋ฒ๋ก ๋๊ฐ๋ค. ์ฑ ๋ก์ง๊ณผ ๋ชฉํน์ด
์์ ํ ๋ถ๋ฆฌ๋๋ค.
๋น์์ ์ค์ ์ค์
git ๊ธฐ๋ก(์ปค๋ฐ 90a3079 "msw ์ค์น", 0d31d81 "mock ๋ฐ์ดํฐ ์ฐ๊ฒฐ")์ ๋ณด๋ฉด,
์์ฒญ๋ณ๋ก ๊ฐ์ง ์๋ต์ ์ ์ํ๋ ํธ๋ค๋ฌ๋ฅผ ์ด๋ ๊ฒ ๋ง๋ค์๋ค.
// mocks/handlers.js
import { rest } from "msw";
const videoId = ["1EgiO-RyMGQ", "grG41kS4MUA", "HmKgFJGn46Q"];
export const handlers = [
// ๊ฒ์ ์์ฒญ โ ๊ฐ์ง videoId ๋ชฉ๋ก
rest.get("/search", (_, res, ctx) => {
return res(ctx.status(200), ctx.json(videoId));
}),
// ์ฌ์๋ชฉ๋ก ์์ฑ ์์ฒญ โ ๊ฐ์ง playlist ๊ฐ์ฒด
rest.post("/make", (_, res, ctx) => {
return res(ctx.status(200), ctx.json(DUMMYPLAYLIST));
}),
];
๊ทธ๋ฆฌ๊ณ ์ด ํธ๋ค๋ฌ๋ค๋ก ์์ปค๋ฅผ ๋ง๋ค์ด ๋ธ๋ผ์ฐ์ ์์ ๋์ด๋ค.
// mocks/server.js
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
์ฑ ์ง์
์ ์์๋ ๊ฐ๋ฐ ๋ชจ๋์ผ ๋๋ง worker.start()๋ฅผ ํธ์ถํ๋ค. ๋น์
์ปค๋ฐ์ ์ฝ๋ ๊ทธ๋๋ก๋ค.
// App.js (๋น์ ์ปค๋ฐ ๊ธฐ์ค)
import { worker } from "./mocks/server";
if (process.env.NODE_ENV === "development") {
worker.start();
}
์ด๋ ๊ฒ ํ๋ฉด ํธ๋ค๋ฌ์ ๋งค์นญ๋๋ ์์ฒญ์ ์ง์ง ์๋ฒ๋ก ๋๊ฐ์ง ์๊ณ ๊ฐ์ง ์๋ต์ ๋ฐ๋๋ค. ํ ๋น๋์ 1๋ ์ ์ด๋ค. ์ง๊ธ ์ฝ๋์์๋ ์ด ๋ถ๋ถ์ด ํต์งธ๋ก ์ฃผ์ ์ฒ๋ฆฌ๋ผ ์๋ค โ ๊ธฐ๋ฅ์ด ์์ ํ๋ ๋ค ๋ชฉํน์ ๋ ๊ฒ์ด๋ค.
๊ณ ๋ฐฑ: ๋ด ์ค์ ์ ์ด์๊ณผ ๊ฑฐ๋ฆฌ๊ฐ ์์๋ค
์์์ "์ฑ ์ฝ๋๊ฐ ๋ชฉํน์ ์กด์ฌ๋ฅผ ์ ํ ๋ชจ๋ฅธ๋ค"๊ณ ํ๋๋ฐ, ์์งํ ๋น์ ๋ด ์ค์ ์ ๊ทธ ์ด์์ ๋ชป ๋ฏธ์ณค๋ค. ์ฝ๋๋ฅผ ๋ค์ ์ด์ด๋ณด๋ ์ด๋ฌ๋ค.
- ์ฑ์
axios.defaults.baseURL์https://www.googleapis.com/youtube/v3๋ก ๋๊ณ ์ ๋ URL๋ก ์์ฒญํ๋๋ฐ, ํธ๋ค๋ฌ๋rest.get("/search")๊ฐ์ ์๋๊ฒฝ๋ก์๋ค. ์๋๊ฒฝ๋ก ํธ๋ค๋ฌ๋ ์ฑ ๋๋ฉ์ธ ๊ธฐ์ค์ด๋ผ googleapis๋ก ๋๊ฐ๋ ์์ฒญ๊ณผ๋ ๋งค์นญ๋์ง ์๋๋ค. ๊ทธ๋์ ๋น์์ ๋ชฉํน์ฉ์ผ๋กfetch("/search")๋ฅผ ๋ฐ๋ก ํธ์ถํด์ ๊ฐ์ง ์๋ต์ ๋ฐ์๋ค โ ์ฆ "์ฑ ์ฝ๋ ๋ฌด๋ณ๊ฒฝ"์ด ์๋๋ผ, ๋ชฉ์ ๋ง์ถฐ ์ฑ ์ฝ๋๋ฅผ ๊ณ ์น ์ ์ด๋ค. - ๋ชฉ ๋ฐ์ดํฐ๋ videoId ๋ฌธ์์ด ๋ฐฐ์ด์ด๋ผ, ์ค์ API ์๋ต ๊ตฌ์กฐ
(
items[].id.videoId)์ ๋ฌ๋๋ค. rest.post("/make")ํธ๋ค๋ฌ๋ ๋ง๋ค์ด๋๊ณ ์ ์ ๋์ํ๋ ์์ฒญ์ด ์์๋ค.
์ ๋๋ก ํ๋ ค๋ฉด ํธ๋ค๋ฌ URL์ ์ค์ ์์ฒญ๊ณผ ๊ฐ์ ์ ๋ URL๋ก ๋ง์ถ๊ณ , ๋ชฉ ๋ฐ์ดํฐ๋ ์ค์ ์๋ต ์คํค๋ง ๊ทธ๋๋ก ๋ง๋ค์ด์ผ ํ๋ค. MSW์ ์ฅ์ ("๊ฒฝ๊ณ์์ ๊ฐ๋ก์ฑ๊ธฐ")์ ์จ์ ํ ๋๋ฆฌ๋ ค๋ฉด ์ด๊ฒ ์ ์ ์กฐ๊ฑด์ธ๋ฐ, ๋น์์ ๊ฑฐ๊ธฐ๊น์ง ๊ฐ์ง ๋ชปํ๋ค.
๊ทธ๋๋ MSW๊ฐ ์ข์๋ ์
- ํ ๋น๋ ์ ์ฝ: ๊ฐ๋ฐ ์ค ๋ฌดํ ์๋ก๊ณ ์นจํด๋ ์ง์ง API๋ฅผ ์ ๋๋ฆฐ๋ค. ์ด๊ฒ ๋์ ํ ์ฒซ ๋ฒ์งธ ์ด์ ์๊ณ , ์ด์คํ ์ค์ ์ผ๋ก๋ ์ด ๋ชฉ์ ์ ๋ฌ์ฑํ๋ค.
- ์คํ๋ผ์ธ/๋ ๋ฆฝ ๊ฐ๋ฐ: ์๋ฒ(๋๋ ์ธ๋ถ API)๊ฐ ์ค๋น ์ ๋์ด๋ ํ๋ก ํธ๋ฅผ ๋จผ์ ๋ง๋ ๋ค. ์๋ต ํํ๋ง ํฉ์ํ๋ฉด ๋ณ๋ ฌ๋ก ์งํ ๊ฐ๋ฅ.
- ์ฃ์ง ์ผ์ด์ค ์ฌํ: 500 ์๋ฌ, ๋น ๊ฒฐ๊ณผ, ๋๋ฆฐ ์๋ต ๊ฐ์ ๊ฑธ ํธ๋ค๋ฌ์์
ctx.status(500),ctx.delay()๋ก ๋ง์๋๋ก ๋ง๋ค ์ ์๋ค. ์ง์ง ์๋ฒ๋ก๋ ์ผ๋ถ๋ฌ ๋ง๋ค๊ธฐ ํ๋ ์ํฉ์ด๋ค.
๋๋์๋ณด๋ฉฐ
๋น์ ๋ด MSW ์ค์ ์ ์ฌ์ค ๋จ์ํ๋ค โ ํธ๋ค๋ฌ ๋์ด ๊ฐ๊ฐ ์ ๋ถ์๊ณ , ์์์ ๊ณ ๋ฐฑํ๋ฏ "์ฑ ์ฝ๋ ๋ฌด๋ณ๊ฒฝ"์ด๋ผ๋ MSW์ ์ง์ง ์ฅ์ ์ ์ด๋ฆฌ์ง ๋ชปํ๋ค. ํ์ง๋ง "๋ชฉํน์ ์ฑ ์ฝ๋๊ฐ ์๋๋ผ ๋คํธ์ํฌ ๊ณ์ธต์์ ํ๋ ๊ฒ"์ด๋ผ๋ ๋ฐ์ ์์ฒด๊ฐ ๊ทธ ๋ค๋ก ๊ฐ๋ฐ ๋ฐฉ์์ ๋ฐ๊ฟจ๋ค. ์ธ๋ถ API์ ์์กดํ๋ ์ฑ์ ๋ง๋ค ๋, ์ง์ง API๋ฅผ ๊ณ์ ๋๋ฆฌ๋ ๋์ ๊ฒฝ๊ณ์์ ๊ฐ๋ก์ฑ๋ฉด ๊ฐ๋ฐ์ด ํจ์ฌ ์์ ๋ก์์ง๋ค.
๊ฐ๋ฐ ๋จ๊ณ์์ ํ ๋น๋ ๊ฑฑ์ ์์ด ๋ง์๊ป ์๋ก๊ณ ์นจํ ์ ์์๋ค๋ ๊ฒ โ ์ง๊ธ์ ๊บผ๋ ๋๊ตฌ์ง๋ง, ๊ทธ๋์ ๊ฐ๋ฐ ๊ฒฝํ์ ํฌ๊ฒ ๋ฐ๊ฟ์คฌ๋ค.
- ๊ด๋ จ ํ๋ก์ ํธ: YouTube Playlist Generator
- ๊ฐ์ด ๋ณด๋ฉด ์ข์ ๊ธ: ๋ฐ๋ณต๋ฌธ์์ API ํธ์ถํ๊ธฐ