[TDD] jest 찍먹해보기3: 실습, Mock Service Worker

April·2022년 12월 16일
0

React🚀

목록 보기
34/43

TDD 방법론에 따라 아래의 순서로 실습해보기

  1. 기능 정의
  2. 테스트 코드 작성
  3. 테스트 실행: 실패
  4. 실제 코드 작성
  5. 테스트 실행: 성공

1. 기능 정의

  • 화면에 체크 박스가 두 개 있다

2. 테스트 코드 작성

해당 체크 박스는 서버에서 받아온 데이터를 토대로 갯수가 정해지고, 여러 개의 체크 박스를 테스트 할 예정이므로 waitFor가 포함된 findAllByRole를 사용.
서버에서 받아온 데이터를 기반으로 화면에 체크박스가 그려지므로 비동기로 처리. async/await

test('fetch option information from server', async () => {
  render(<MyComponent />);

  // 체크박스 가져오기
  const optionCheckboxes = await screen.findAllByRole('checkbox');

  expect(optionCheckboxes).toHaveLength(2);
});

3. 테스트 코드 실행: 실패

구현된 실체가 없으므로 당연 실패..

4. 실제 코드 작성

function MyComponent() {
 // .... 생략

  const loadItems = async () => {
    try {
      let response = await axios.get(`http://localhost:5000/products`);
      setItems(response.data);
    } catch (error) {
      setError(true);
    }
  };

  if (error) {
    return <ErrorBanner message="에러가 발생했습니다." />;
  }

  return items.map((item) => (
    <>
      <input
            type="checkbox"
            checked={checked}
            onChange={(e) => setChecked(e.target.checked)}
            id="confirm-checkbox"
      />
      <label htmlFor="confirm-checkbox">{item.name}</label>
      <br />
      <button disabled={!checked} type="submit">
       확인
      </button>
    </>
  ));
}

export default MyComponent;

5. 테스트 코드 실행: 성공



Mock Service Worker

백엔드에서 데이터를 가져오는 부분을 테스트하기 위해서는 실제로 서버에 호출하는 end-to-end 테스트를 할 수 있지만
서버에 요청을 보낼 때 그 요청을 가로채서 Mock Service Woker라는 것으로 요청을 처리하고
모의 응답(mocked response)을 보내주는 방법으로도 테스트 진행이 가능하다

MSW 작동 방식

https://mswjs.io/docs/

  • 브라우저에 서비스 워커를 등록하여 외부로 나가는 네트워크 리퀘스트를 감지.
  • 그리고 그 요청을 실제 서버로 갈 때 중간에 가로채서 (intercept) MSW 클라이언트 사이드 라이브러리로 보낸다.
  • 그 후 등록된 핸들러에서 요청을 처리한 후 모의 응답을 브라우저로 보낸다

실습

두 가지 방법으로 사용할 수 있는데, 하나는 브라우저와 통합하는 방법이고 또 하나는 노드와 통합(Jest 사용하는 테스트 환경)하는 방법이 있다.
그 중 jest 실습 중이므로 노드와 통합(Jest 사용하는 테스트 환경)하는 방법으로 진행.

1. msw 설치

npm install msw --save-dev
# or
yarn add msw --dev

2. 핸들러 생성

restAPI로 실습했으나 graphql도 가능

  • req: 매칭 요청에 대한 정보
  • res: 모의 응답을 생성하는 기능적 유틸리티
  • ctx: 모의 응답의 상태 코드, 헤더, 본문 등을 설정하는 데 도움이 되는 함수 그룹

포트 5000에서 서버가 작동되어야 에러 없이 진행 가능.. 서버가 없다면 서버가 없다고 에러 발생..

// src > mocks > handlers.js

import { rest } from 'msw';

export const handlers = [
  // 
  rest.get('http://localhost:5000/products', (req, res, ctx) => {
    return res(
      ctx.json([
        {
          name: 'America',
          imagePath: '/images/america.jpeg',
        },
        {
          name: 'England',
          imagePath: '/images/england.jpeg',
        },
      ])
    );
  }),
  rest.get('http://localhost:5000/options', (req, res, ctx) => {
    return res(
      ctx.json([
        {
          name: 'Insurance',
        },
        {
          name: 'Dinner',
        },
      ])
    );
  }),
  rest.post('http://localhost:5000/order', (req, res, ctx) => {
    let dummyData = [{ orderNumber: 2131234324, price: 2000 }];
    return res(ctx.json(dummyData));
  }),
];

참고: 공식문서의 graphql 샘플 코드

// 공식문서의 샘플 코드
import { setupWorker, graphql } from 'msw'

const worker = setupWorker(
  graphql.mutation('Login', (req, res, ctx) => {
    const { username } = req.variables
    return res(
      ctx.data({
        user: {
          username,
          firstName: 'John'
        }
      })
    )
  })
)

3. 서버 생성

// src > mocks > server.js

import { setupServer } from "msw/node";
import { handlers } from "./handlers";

// mocking server 생성
export const server = setupServer(...handlers)

4. API mocking 설정

// src > setupTests.js

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

import { server } from "./mocks/server";

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

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

Custom Render 만들기

contextAPI 등을 사용할 경우 테스트 케이스에도 context가 필요하기 때문에
컴포넌트 마다 wrapper로 감싸주는 것은 비효율적이므로 Custom Render를 만들어서 테스트 가능

// src > test-utils.js

import { render } from "@testing-library/react";
import { OrderContextProvider } from "./contexts/OrderContext";

const customRender = (ui, options) => 
    render(ui, {wrapper: OrderContextProvider, ...options})

export * from '@testing-library/react'

export {customRender as render}
// src > contexts > OrderContext.js

import { createContext } from 'react';

export const OrderContext = createContext();

export function OrderContextProvider(props) {
 // ... 생략

  return <OrderContext.Provider value={value} {...props} />;
}
// App.js
import { OrderContextProvider } from "./contexts/OrderContext";

function App() {
  const [step, setStep] = useState(0);
  
  return (
      <OrderContextProvider>
         <MyComponent />
      </OrderContextProvider>
  );
}



profile
🚀 내가 보려고 쓰는 기술블로그

0개의 댓글