MSW

ํ•™์Šต ํ‚ค์›Œ๋“œ

  • Service Worker

  • MSW(Mock Service Worker)

  • polyfill(ํด๋ฆฌํ•„)

Service Worker

  • ์›น ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ, ๋ธŒ๋ผ์šฐ์ €, ๊ทธ๋ฆฌ๊ณ  (์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ) ๋„คํŠธ์›Œํฌ ์‚ฌ์ด์˜ ํ”„๋ก์‹œ ์„œ๋ฒ„ ์—ญํ™œ ์ˆ˜ํ–‰

  • ์„œ๋น„์Šค ์›Œ์ปค๋Š” ์ถœ์ฒ˜์™€ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ๋“ฑ๋กํ•˜๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์›Œ์ปค๋กœ์„œ JavaScript๋กœ ์ž‘์„ฑ ๋œ ํŒŒ์ผ

์„œ๋น„์Šค ์›Œ์ปค๋Š” ์—ฐ๊ด€๋œ ์›น ํŽ˜์ด์ง€/์‚ฌ์ดํŠธ๋ฅผ ํ†ต์ œํ•˜์—ฌ ํƒ์ƒ‰๊ณผ ๋ฆฌ์†Œ์Šค ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ์ˆ˜์ •ํ•˜๊ณ , ๋ฆฌ์†Œ์Šค๋ฅผ ๊ต‰์žฅํžˆ ์„ธ๋ถ€์ ์œผ๋กœ ์บ์‹ฑํžŒ๋‹ค.

โ‡’ ์›น ์•ฑ์ด ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•ด์•ผ ํ•˜๋Š”์ง€ ์™„๋ฒฝํ•˜๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. (๋Œ€ํ‘œ์ ์ธ ์ƒํ™ฉ์€ ๋„คํŠธ์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ)

MSW(Mock Service Worker)

๐ŸŒŽ MSW ์‚ฌ์šฉ ๋ฐฐ๊ฒฝ(feat.Mocking)

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๋‹ค๋ณด๋ฉด ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ๊ณผ์˜ ์ข…์†์ ์ธ ๋ถ€๋ถ„์ด ์ƒ๊ธฐ๊ธฐ ๋งˆ๋ จ์ด๋‹ค. (๋Œ€ํ‘œ์ ์œผ๋กœ ๋ฐฑ์—”๋“œ์˜ API๋ฅผ ํ™œ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ)

๊ธฐํš์ž : XX ์ž‘์—…์€ ์–ด๋–ป๊ฒŒ ์ง„ํ–‰ ์ค‘์ธ๊ฐ€์š”? ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž : ๊ทธ๊ฒŒโ€ฆ ์•„์ง API๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•„์„œ ๋‹ค์Œ ์ฃผ๊นŒ์ง€๋Š” ๊ธฐ๋‹ค๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.....

์˜ˆ์ƒํ•œ ๊ธฐ๊ฐ„๋ณด๋‹ค API ๊ฐœ๋ฐœ์— ์‹œ๊ฐ„์ด ๋” ํ•„์š”ํ•ด์ง„ ๊ฒฝ์šฐ, ๊ทธ ์‹œ๊ฐ„๋งŒํผ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜์ง€ ๋ชปํ•˜๋Š” ์ƒํ™ฉ์ด ์ƒ๊ฒจ๋‚˜๊ธฐ๋„ ํ•œ๋‹ค.

๊ณ„์† ๊ธฐ๋‹ค๋ฆด ์ˆ˜๋Š” ์—†๊ธฐ์— Mocking์„ ํ†ตํ•ด ์œ„์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ–ˆ๋‹ค.

Thinking in React ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด API ์š”์ฒญ ์ฝ”๋“œ ๋ชจํ‚น์ฒ˜๋Ÿผ ์ง์ ‘์ ์œผ๋กœ ๋‚ด๋ถ€ ๋กœ์ง์— ์ง์ ‘ Mockingํ•ด์„œ ํ•„์š”ํ•œ ํ™”๋ฉด์— ๋ถ™์ด๋Š” ๋ฐฉ์‹์ด ์žˆ์ง€๋งŒ,

  • ์„œ๋น„์Šค ๋กœ์ง์— ์ง์ ‘ Mocking์„ ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค ๋กœ์ง์„ ์ˆ˜์ • ํ•„์š”

  • HTTP ๋ฉ”์†Œ๋“œ์™€ ๋„คํŠธ์›Œํฌ์˜ ์‘๋‹ต ์ƒํƒœ์— ๋”ฐ๋ผ ๊ฐ๊ฐ ๋Œ€์‘ํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š๋‹ค.

  • (Mocking์œผ๋กœ ๋งŒ๋“  ๊ฒฐ๊ณผ๋ฌผ)ํ™”๋ฉด์— ๋Œ€ํ•œ ํ…Œ์ŠคํŒ… ๋ฐ ๋””๋ฒ„๊น… ์‹œ์— ์–ด๋ ค์›€์ด ๋ฐœ์ƒ .....

๐Ÿ’ก ๊ฒฐ๊ตญ ์‹ค์ œ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋„คํŠธ์›Œํฌ ์ˆ˜์ค€์—์„œ Mocking ํ•˜๊ธธ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๊ณผ์ •์—์„œ Request์— ๋Œ€ํ•œ Mocking์ด ๊ฐ€๋Šฅํ•œ MSW๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๐Ÿ“– MSW๋Š” ๋ฌด์—‡์ธ๊ฐ€?

  • Mock Service Worker์˜ ์•ฝ์ž

  • API Mocking ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„์„œ ๋ชจ์˜ ์‘๋‹ต(Mocked response)์„ ๋ณด๋‚ด์ฃผ๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰

โš™๏ธ MSW ์„ค์น˜ ๋ฐ ์„ค์ •

1. MSW ํŒจํ‚ค์ง€ ์„ค์น˜

npm i -D msw@0.36.4 

2. jest.config.js ํŒŒ์ผ์˜ โ€œsetupFilesAfterEnvโ€ ์†์„ฑ์— setupTests.ts ํŒŒ์ผ ์ถ”๊ฐ€

// jest.config.js

module.exports = {
 testEnvironment: 'jsdom',
 setupFilesAfterEnv: [
  '@testing-library/jest-dom/extend-expect',
  '<rootDir>/src/setupTests.ts', //๐Ÿ‘ˆ๐Ÿป ์ด๋ถ€๋ถ„์ถ”๊ฐ€
 ],

3. setupTests.ts ํŒŒ์ผ ์ƒ์„ฑ

touch src/setupTests.ts
// src/setupTests.ts

import server from './mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

afterAll(() => server.close());

afterEach(() => server.resetHandlers());
  • beforeAll : Jest ์‹œ์ž‘ํ•  ๋•Œ ๋งจ ์ฒ˜์Œ์— ์‹คํ–‰

    • server.listen : ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „ ๋ชจํ‚น ํ™œ์„ฑํ™”

    • onUnhandledRequest: 'error' : handler๋ฅผ ์•ˆ ์žก์•˜์„ ๋•Œ ์˜ค๋ฅ˜ ๋‚ด๋„๋ก ์„ค์ •

  • afterAll : ์ „๋ถ€ ๋๋‚  ๋•Œ ์‹คํ–‰

    • server.close: ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋œ ํ›„ ๋„ค์ดํ‹ฐ๋ธŒ ์š”์ฒญ ๋ฐœํ–‰ ๋ชจ๋“ˆ ๋ณต์›(?)

  • afterEach : ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰

    • server.resetHandlers : ํ…Œ์ŠคํŠธ ์‚ฌ์ด์— ๋ชจ๋“  ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์žฌ์„ค์ •

4. ๐Ÿ“ src/mocks/ server.ts ํŒŒ์ผ ์ƒ์„ฑ

mkdir src/mocks

touch src/mocks/server.ts
// src/mocks/server.ts

import { setupServer } from 'msw/node';

import handlers from './handlers'

const server = setupServer(...handlers);

export default server;

5. ๐Ÿ“ src/mocks/ handlers.ts ํŒŒ์ผ ์ƒ์„ฑ

touch src/mocks/handlers.ts
// src/mocks/handlers.ts 

import { rest } from 'msw';

const BASE_URL = 'http://localhost:3000';

const handlers = [
  rest.get(`${BASE_URL}`, (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({ products }),
    );
  }),
];

export default handlers;

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป Thinking in React ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด REST API ๋ชจํ‚นํ•˜๊ธฐ

1. MSW ์„ค์น˜ ๋ฐ ํŒŒ์ผ ์ƒ์„ฑ ๋ฐ ์„ค์ • ์™„๋ฃŒ

โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ jest.config.js โœ…
โ”œโ”€โ”€ src
โ”‚   โ”œโ”€โ”€ App.tsx
โ”‚   โ”œโ”€โ”€ App.test.tsx โœ…
โ”‚   โ”œโ”€โ”€ setupTests.ts โœ…
โ”‚   โ”œโ”€โ”€ hooks ๐Ÿ“
โ”‚   โ”‚   โ”œโ”€โ”€ useFetchProducts.ts 
โ”‚   โ”œโ”€โ”€ mocks ๐Ÿ“
โ”‚   โ”‚   โ”œโ”€โ”€ handlers.ts โœ…
โ”‚   โ”‚   โ””โ”€โ”€ server.ts โœ…

2. Thinking in React ์˜ˆ์ œ์˜ Mock Date ๊ธฐ์ค€์œผ๋กœ handlers.ts ์ž‘์„ฑ

// 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), 
            ctx.json({products}), // ์œ„์˜ ์š”์ฒญ์„ ์ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
        );
    }),
];

export default handlers;
  1. App.test.tsx ์ˆ˜์ •

  • jest.mock ์ œ๊ฑฐ

  • waitFor ์ถ”๊ฐ€ (~ ๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋Š” ์ƒํƒœ)

  • ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ ํƒ€์ž…์ด Promise๋กœ ๋˜์–ด ์žˆ์–ด์„œ async/await ํ•„์š”

// App.test.tsx

import {render, screen, waitFor} from '@testing-library/react';
import App from './App';

// jest.mock ๋ถˆํ•„์š”
// jest.mock('./hooks/useFetchProducts', () => () => fixtures.products);
//jest.mock('./hooks/useFetchProducts');

test('App', async () => {
    render(<App / >);

    await waitFor(() => {
        screen.getByText('Apple');
    });
});

4. ๐Ÿšจ Error ๋ฐœ์ƒ

// hooks/useFetchProducts.ts

export default function useFetchProducts() {
    const url = 'http://localhost:3000/products';
    const {data, error} = useFetch<ProductsResult>(url);
    console.log({error}); // ๐Ÿ‘ˆ๐Ÿป ์ถ”๊ฐ€ํ•ด์„œ ํ™•์ธํ•ด๋ณด๋‹ˆ, Error
    if (!data) {
        return [];
    }

    return data.products;
}

ReferenceError: fetch is not defined ๋ฐœ์ƒ

node.js ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ ์ค‘์ด์—ˆ๋‹ค. ์ตœ์‹ ๋ฒ„์ „์˜ node๋Š” fetch๋ฅผ ์ง€์›ํ•˜์ง€๋งŒ, ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋ฒ„์ „์˜ Node๋Š” ์ง€์›ํ•˜์ง€ ์•Š์•„ ๋‚˜ํƒ€๋‚œ error! (๐Ÿ’ก Fetch๋Š” ๋ธŒ๋ผ์šฐ์ €[window]์—์„œ๋งŒ ์ง€์›)

5. Github์—์„œ ๋งŒ๋“  Fetch Polyfill(ํด๋ฆฌํ•„)์„ ์‚ฌ์šฉํ•ด ํ•ด๊ฒฐ

npm i -D whatwg-fetch
  • setupTests.ts ํŒŒ์ผ ์ตœ์ƒ์œ„์— import๋กœ ์ ์šฉ

import 'whatwg-fetch'

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด์ „ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ตœ์‹  ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ฝ”๋“œ

  • ์ƒˆ๋กœ์šด ๋ฌธ๋ฒ•์„ ๋‚ฎ์€ ๋ฒ„์ „์—์„œ๋„ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๋งŒ๋“  ์ฝ”๋“œ

๐Ÿ” ์ฐธ๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์‹œ?

  • ๋ฐ”๋ฒจ๊ณผ ๊ฐ™์€ ํŠธ๋žœ์Šค ํŒŒ์ผ๋Ÿฌ

์ตœ์‹  ์ŠคํŽ™์œผ๋กœ ์ž‘์„ฑ๋œ ์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋ฅผ ๋ช…์‹œํ•œ ๋ฒ„์ „์— ๋”ฐ๋ผ ์žฌ์ž‘์„ฑ ํ•ด์ค๋‹ˆ๋‹ค. ๋‚ฎ์€ ๋ฒ„์ „์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์—์„œ๋„ ํ”„๋กœ๊ทธ๋žจ์ด ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์–ด ์„œ๋น„์Šค๊ฐ€ ๋ฌธ์ œ์—†์ด ์ œ๊ณต๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ๋ฐ”๋ฒจ์—๋Š” core-js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํƒ‘์žฌ๋˜์–ด es6 ์ดํ›„์˜ ๋ฌธ๋ฒ•๋“ค์— ํด๋ฆฌํ•„์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”— ์ฐธ๊ณ 

Last updated