3. MSW
1. Service Worker
Service Worker API Workers overview
์๋น์ค ์์ปค๋ ์น ์์ฉ ํ๋ก๊ทธ๋จ, ๋ธ๋ผ์ฐ์ , ๊ทธ๋ฆฌ๊ณ (์ฌ์ฉ ๊ฐ๋ฅํ ๊ฒฝ์ฐ) ๋คํธ์ํฌ ์ฌ์ด์ ํ๋ก์ ์๋ฒ ์ญํ ์ ํจ

ํน์ง
ํจ๊ณผ์ ์ธ ์คํ๋ผ์ธ ๊ฒฝํ์ ์์ฑ
๋คํธ์ํฌ ์์ฒญ์ ๊ฐ๋ก์ฑ์ ๋คํธ์ํฌ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ์ ๋ฐ๋ผ ์ ์ ํ ํ๋์ ์ทจํจ
์๋ฒ์ ์์ฐ์ ์ ๋ฐ์ดํธ
ํธ์ ์๋ฆผ๊ณผ ๋ฐฑ๊ทธ๋ผ์ด๋ ๋๊ธฐํ API๋ก์ ์ ๊ทผ๋ ์ ๊ณต
๋ณด์ ์์ ์ด์ ๋ก HTTPS์์๋ง ๋์
๋คํธ์ํฌ ์์ฒญ์ ์์ ํ ์ ์๋ค๋ ์ ์์ ์ค๊ฐ์ ๊ณต๊ฒฉ์ ๊ต์ฅํ ์ทจ์ฝํ๊ธฐ ๋๋ฌธ
์น ์์ปค vs ์๋น์ค ์์ปค
๊ณตํต์
Web API ์ค ํ๋
์น ์ฌ์ดํธ์์ ์ฌ์ฉํ ์ ์๋ ์์ปค
๋ ๋ค ๋ณด์กฐ ์ค๋ ๋์์ ์คํ๋๋ฏ๋ก ๊ธฐ๋ณธ ์ค๋ ๋์ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ์ฐจ๋จํ์ง ์๊ณ JavaScript ์ฝ๋๋ฅผ ์คํ(๋ ผ ๋ธ๋กํน)
Window ๋ฐ Document objects์ ๋ํ ์ก์ธ์ค ๊ถํ์ด ์์ผ๋ฏ๋ก, DOM๊ณผ ์ง์ ์ํธ ์์ฉํ ์ ์์ผ๋ฉฐ(DOM์ ์ ๊ทผ ๋ถ๊ฐ) ๋ธ๋ผ์ฐ์ API์ ๋ํ ์ก์ธ์ค๊ฐ ์ ํ๋จ
์ฐจ์ด์
์๋ช
์น ์์ปค๊ฐ ์ํ ํญ๊ณผ ๋ฐ์ ํ๊ฒ ์ฐ๊ฒฐ
๋ ๋ฆฝ์
ํญ ์ข ๋ฃ ์
์ข ๋ฃ๋จ
๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณ์ ์คํ ๊ฐ๋ฅ
๊ฐฏ์
์ฌ๋ฌ ์น ์์ปค ์์ฑ ๊ฐ๋ฅ
๋ฑ๋ก๋ ๋ฒ์์ ๋ชจ๋ ํ์ฑ ํญ์ ์ ์ด
๋คํธ์ํฌ ์์ฒญ
-
๋คํธ์ํฌ ์์ฒญ์ ๊ฐ๋ก์ฑ(fetch์ด๋ฒคํธ๋ฅผ ํตํด), ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํธ์ API ์ด๋ฒคํธ๋ฅผ ์์
์น ์์ปค์ ์๋ช ์ ์น ์์ปค๊ฐ ์ํ ํญ๊ณผ ๋ฐ์ ํ๊ฒ ์ฐ๊ฒฐ๋์ด ์๋ ๋ฐ๋ฉด, ์๋น์ค ์์ปค์ ์๋ช ์ฃผ๊ธฐ๋ ๋ ๋ฆฝ์ ์น ์์ปค๊ฐ ์คํ ์ค์ธ ํญ์ ๋ซ์ผ๋ฉด ์ข ๋ฃ๋์ง๋ง, ์๋น์ค ์์ปค๋ ์ฌ์ดํธ์ ์ด๋ ค ์๋ ํ์ฑ ํญ์ด ์๋ ๊ฒฝ์ฐ์๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณ์ ์คํ ๊ฐ๋ฅ
ํ์ด์ง๋ ์ฌ๋ฌ ์น ์์ปค๋ฅผ ์์ฑํ ์ ์์ง๋ง, ๋จ์ผ ์๋น์ค ์์ปค๋ ๋ฑ๋ก๋ ๋ฒ์์ ๋ชจ๋ ํ์ฑ ํญ์ ์ ์ด
์น ์์ปค์ ๋ฌ๋ฆฌ ์๋น์ค ์์ปค๋ฅผ ์ฌ์ฉํ๋ฉด fetch์ด๋ฒคํธ๋ฅผ ํตํด ๋คํธ์ํฌ ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ , ๋ฐฑ๊ทธ๋ผ์ด๋์์ push์ด๋ฒคํธ๋ฅผ ํตํด Push API ์ด๋ฒคํธ๋ฅผ ์์
2. MSW(Mock Service Worker)
MSW ์์ฌ๋์ Mock Service Worker (MSW)
express ๋ณด๋ค ์กฐ๊ธ ๋ถํธํจ ์ฝ๋ ๋ ๋ฒจ์ด ์๋๋ผ ๋คํธ์ํฌ ๋ ๋ฒจ์์ ๊ฐ์ง ๊ตฌํ(ํ๋ก์๋ฅผ ์ด์ฉ) ์คํ๋ผ์ธ ์์ ๋ฑ์ ์ง์ํ๊ธฐ ์ํ ์๋น์ค ์์ปค์ ๊ธฐ๋ฅ์ ์ ์ฉํ๊ฒ ํ์ฉํ ๊ฒ Node, ๋ธ๋ผ์ฐ์ ๋ ๋ค ์ง์
๐ฅ Express vs MSW
๋จ์ํ ์์ ์๋ฒ๋ฅผ ๋ง๋ค ๊ฑฐ๋ผ๋ฉด Express๋ฅผ ์ฐ๋ ๊ฒ ๋ ๋ซ์ง๋ง, MSW๋ ํ ์คํธ ์ฝ๋๋ ์ง์ํ๋ฉด์ ๊ฒธ์ฌ๊ฒธ์ฌ ์น ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํ๋ ์ฉ๋๋ก๋ ๋์์ง ์์ ์ ํ
MSW๋ jest์ ํ ์คํธ ํ๊ฒฝ(Node.js ๊ธฐ๋ฐ) ์ธ์ ์น ๋ธ๋ผ์ฐ์ ๋ ์ง์ API ์คํ์ ๋์์ง๋ง ์์ง ๊ตฌํ๋์ง ์์ ๊ฒฝ์ฐ ์์๋ก ์ฌ์ฉํ ์ ์์
์ฌ์ฉ ๋ฐฉ๋ฒ
1. MSW ํจํค์ง ์ค์น
npm i -D msw
2. server.ts
ํ์ผ ์์ฑ
server.ts
ํ์ผ ์์ฑsrc/mocks/server.ts ๊ฒฝ๋ก์ ์์ฑ
// src/mocks/server.ts
import {setupServer} from 'msw/node';
// Import handlers from './handlers';
const handlers = []; // ์์๋ก ์ ์ด๋
// This configures a request mocking server with the given request handlers.
const server = setupServer(...handlers);
export default server;
3. setupTests.ts ํ์ผ ์์ฑ
// src/setupTests.ts
import server from './mocks/server.ts';
// Establish API mocking before all tests.
beforeAll(() => server.listen({onUnhandledRequest: 'error'}));
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());
// Clean up after the tests are finished.
afterAll(() => server.close());
beforeAll : Jest ์์ํ ๋ ๋งจ ์ฒ์์ ์คํ
onUnhandledRequest: 'error'
: handler๋ฅผ ์ ์ก์์ ๋ ์ค๋ฅ ๋ด๋๋ก ์ค์
afterEach : ๊ฐ ํ ์คํธ๊ฐ ๋๋ ๋๋ง๋ค ์คํ
afterAll : ์ ๋ถ ๋๋ ๋ ์คํ
3. jest.config.js
ํ์ผ์ โsetupFilesAfterEnvโ ์์ฑ์ setupTests.ts
ํ์ผ ์ถ๊ฐ
jest.config.js
ํ์ผ์ โsetupFilesAfterEnvโ ์์ฑ์ setupTests.ts
ํ์ผ ์ถ๊ฐ// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
'<rootDir>/src/setupTests.ts',
],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', {
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
}],
},
};
3. REST API ๋ชจํนํ๊ธฐ
โ ๏ธ ๋๋ฌด ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋ฉํ๋ฉด ์ฌ์ค์ ๋ฐฑ์๋๋ฅผ ๊ฐ๋ฐํ๊ฒ ๋๋, ์ด ๋ถ๋ถ์ ์ฃผ์ํ ๊ฒ
ํด๋ ๊ตฌ์กฐ
โโโ package.json
โโโ package-lock.json
โโโ jest.config.js โ
โโโ src
โ โโโ main.tsx
โ โโโ App.tsx
โ โโโ App.test.tsx โ
โ โโโ setupTests.ts โ
โ โโโ components ๐
โ โโโ hooks ๐
โ โโโ mocks ๐
โ โ โโโ handlers.ts โ
โ โ โโโ server.ts โ
handlers.ts ํ์ผ ์์ฑ
์ง์ง๊ฐ์ ๊ฒ
์ด์ง ์ง์ง
๊ฐ ์๋
์ง์ง๋ ํ
์คํธ๊ฐ ํ์ํจ โ E2E ํ
์คํธ
// src/mocks/handlers.ts
import {rest} from 'msw';
// import fixtures from '../../fixtures';
const BASE_URL = 'http://localhost:3000';
const handlers = [
rest.get(`${BASE_URL}/products`, (req, res, ctx) => {
// const { products } = fixtures; // fixtures๋ฅผ ์ฌ์ฉํด๋ ๋จ
const products = [
{
category: 'Fruits', price: '$1', stocked: true, name: 'Apple',
},
];
return res(
ctx.status(200), // ์์ด๋ ๋จ(๊ธฐ๋ณธ ์ค์ ์ด 200)
ctx.json({products}), // ์์ ์์ฒญ์ ์ด ํํ๋ก ๋ฐํฉ
);
}),
];
export default handlers;
App.test.ts ํ์ผ ์์
// App.test.ts
import {render, screen, waitFor} from '@testing-library/react';
import App from './App';
// jest.mock ๋ถํ์
test('App', async () => {
render(<App / >);
await waitFor(() => {
screen.getByText('Apple');
});
});
waitFor : ~ ๊ฐ ๋ ๋๊น์ง ๋๊ธฐ
์ฝ๋ฐฑํจ์์ ํ์ ์ด Promise๋ก ๋์ด ์์ด์ async/await ํ์
์ฃผ์์ & polyfill ์ด์ฉํ๊ธฐ
// hooks/useFetchProducts.ts
export default function useFetchProducts() {
const url = 'http://localhost:3000/products';
const {data, error} = useFetch<ProductsResult>(url);
console.log({error});
if (!data) {
return [];
}
return data.products;
}
๐จ error: ReferenceError: fetch is not defined
fetch๋ ์๋์ฐ์ ์๋ ๊ฒ ๋ธ๋ผ์ฐ์ ์์๋ ๋์ง๋ง Node์์๋ ์ค๋ฅ Node ์ต์ ๋ฒ์ ์ fetch๋ฅผ ์ง์ํ์ง๋ง, ํ์ฌ ์ฌ์ฉ ์ค์ธ Node ๋ฒ์ ์์๋ ์ง์ X
โ polyfill(ํด๋ฆฌํ) ์ ์ด์ฉํด ํด๊ฒฐ
GitHub์์ ๋ง๋ fetch polyfill
์ค์น
npm i -D whatwg-fetch
setupTests.ts
ํ์ผ์ import
setupTests.ts
ํ์ผ์ importimport 'whatwg-fetch'
hooks/useFetchProducts.ts
ํ์ผ ์์ํ๋ก ๋๋๋ฆฌ๊ธฐ
hooks/useFetchProducts.ts
ํ์ผ ์์ํ๋ก ๋๋๋ฆฌ๊ธฐexport default function useFetchProducts() {
const url = 'http://localhost:3000/products';
const {data} = useFetch<ProductsResult>(url);
if (!data) {
return [];
}
return data.products;
}
3. polyfill(ํด๋ฆฌํ)
MDN - Polyfill ๋ชจ๋ ์๋ฐ์คํฌ๋ฆฝํธ - ํด๋ฆฌํ GitHub์์ ๋ง๋ fetch polyfill
๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํ์ง ์๋ ์ด์ ๋ธ๋ผ์ฐ์ ์์ ์ต์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฐ ํ์ํ ์ฝ๋ (์ผ๋ฐ์ ์ผ๋ก ์น์ JavaScript)
์๋ฐ์คํฌ๋ฆฝํธ ์์ง์ ๋ง๋๋ ๊ฐ ์กฐ์ง์ ๋๋ฆ๋๋ก ์ฐ์ ์์๋ฅผ ๋งค๊ฒจ ๋ช ์ธ์ ๋ด ์ด๋ค ๊ธฐ๋ฅ์ ๋จผ์ ๊ตฌํํ ์ง ๊ฒฐ์ ๋ช ์ธ์์ ๋ฑ๋ก๋ ๊ธฐ๋ฅ๋ณด๋ค ์ด์(draft)์ ์๋ ์ ์์ ๋จผ์ ๊ตฌํํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ ๊ฒฝ์ฐ๋ ์กด์ฌ ๊ตฌํ ๋๋๊ฐ ๋์์ ์ด๋ฐ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๊ฒฝ์ฐ๋ ์์ง๋ง, ๊ตฌ๋ฏธ๋ฅผ ๋น๊ธฐ์ง ์์ ์ด๋ฐ ๊ฒฐ์ ์ ๋ด๋ฆฌ๊ธฐ๋ ํจ ์์ง์ด ํ์ค ์ ์ฒด๋ฅผ ์ง์ํ์ง ์๊ณ ์ผ๋ถ๋ง ์ง์ํ๋ ๊ฒ์ ํํ ์ผ
Last updated