Story Controls에서 MSW 응답 조작하기

Hyehyeon Moon·2023년 3월 20일
4
post-thumbnail

배경

Storybook과 MSW를 함꼐 사용하면서 많은 스토리를 생성하는 데 번거로움을 느꼈습니다.
즉, 20개의 msw 응답에 따른 컴포넌트 UI를 보기 위해서는 20개의 story를 생성해야만 했습니다.

"응답 내 값 1개를 바꾸기 위해서 story를 하나 더 다시 만들어야 한다니?!"란 불편함에서 해결방안을 찾기 시작했습니다.

개념 체크

MSW란

MSW(Mock Service Worker)는 API Mocking 라이브러리로, 서버 네트워크 요청을 가로채서 모의 응답(Mocked response)을 보내주는 역할을 합니다. 동작 원리는 Service Worker를 통해 HTTP 요청을 가로채어 응답을 보내줍니다.

MSW storybook Addon이란

MSW Storybook Addon은 Storybook에서 MSW 사용을 가능하게 해주는 라이브러리입니다.

문제 상황

msw 응답 수만큼 story를 만들어야 하는 불편함이 있었습니다.

예시를 들어 설명해보겠습니다.

Sample component에서는 '/max/amount' api를 통해 maxAmount 값을 받고 있습니다. maxAmount'500', '5000', '10000' 세 가지 값을 가지고 있습니다. Sample component는 maxAmount 값에 따라서 다른 UI를 보여주어야 하며 현재 저는 이를 story를 통해 확인하고 싶습니다.

import { rest } from 'msw';

const mocking = (maxAmount) =>
  rest.get('/max/amount', (req, res, ctx) => res(ctx.json({ maxAmount })));

const Template = () => <Sample />;

export const smallAmount = Template.bind({}); // 500 maxAmount 값을 응답으로 받는 경우
smallAmount.parameters = {
  msw: {
    handlers: [mocking(500)]
  }
}

export const mediumAmount = Template.bind({}); // 5000 maxAmount 값을 응답으로 받는 경우
smallAmount.parameters = {
  msw: {
    handlers: [mocking(5000)]
  }
}

export const largeAmount = Template.bind({}); // 10000 maxAmount 값을 응답으로 받는 경우
smallAmount.parameters = {
  msw: {
    handlers: [mocking(10000)]
  }
}

세 개의 maxAmount 값 각각을 보여주는 세 개의 story가 생성된 것을 볼 수 있습니다.

저의 고민은 한 개의 story에서 편하게 세 개의 maxAmount 응답을 받고 싶다는 것이었습니다.

해결 과정

  1. msw-storybook-addon에서 worker를 끄집어내기
import { getWorker } from 'msw-storybook-addon';

const worker = getWorker(); 
  1. control을 이용해 응답 조작하기
import { rest } from 'msw';
import { getWorker } from 'msw-storybook-addon';

const worker = getWorker(); // msw-storybook-addon에서 worker를 끄집어냅니다

const mocking = (maxAmount) =>
  rest.get('/max/amount', (req, res, ctx) => res(ctx.json({ maxAmount })));

export const Default = ({ maxAmount /*controls에서 선택한 값이 넘어옵니다 */}) => { 
  const handler = mocking(maxAmount);
  worker.use(handler); // worker에 handler를 넘겨줍니다
  return <Sample />;
};
Default.args = {
  maxAmount: 500,
};
Default.argTypes = { // controls에서 maxAmount 값을 선택할 수 있도록 합니다
  maxAmount: {
    options: ['500', '5000', '10000'],
    control: { type: 'select' },
  },
};
  1. key를 붙여주기

control을 이용해 maxAmount 값을 바꾸었을 때, 새롭게 api 요청을 보내어 응답을 받아와야 합니다. 따라서 key를 component에 추가하여 강제 언마운트 시키고 새롭게 렌더링하도록 만들었습니다.

export const Default = ({ maxAmount }) => {
  const handler = mocking(maxAmount);
  worker.use(handler); 
  return <Sample key={`${maxAmoount}`}/>; // maxAmount가 바뀔 때마다 api 요청이 가도록 key를 부여합니다
};

짜잔! 다음과 같이 Controls에서 간단한 조작을 통해 응답이 바뀌고, 덩달아 UI도 바뀌는 것을 보실 수 있습니다.

장단점

장점

  • 한 개의 story에서 여러 msw 응답을 편하게 조작 가능

위의 예시에서는 maxAmount의 값이 세 가지뿐이었습니다. 하지만 실제 프로젝트에서는 많게는 100가지도 넘는 다양한 경우들이 있을 수 있습니다. 개발자에게도, storybook을 보는 디자이너/기획자도 story 100개를 보는 것보다는 1개의 story에서 응답을 조작하는 것이 편합니다.

단점

  • 상위 컴포넌트에 집중되는 mocking, 소홀해지는 하위 컴포넌트 story

지금까지 위의 예시로 나온 Sample 컴포넌트의 내부 구조는 다음과 같았다고 가정해 봅시다.

// 예시 코드입니다. 흐름만 이해해 주세요.
export const Sample = () => {
  const [data, setData] = useState();
  const getData = async () => {
    await fetch("/max/amount").then((res) => setData(res.json()));
  }
  
  useEffect(() => {
    getData();
  }, [])

  return <div >{data?.maxAmount}</div>;

만약 Sample 컴포넌트의 내부 구조가 아래와 같이 변경된다면 Sample component story에 mocking이 필요할까요?

export const Sample = () => {
  const [data, setData] = useState();
  const getData = async () => {
    await fetch('http://localhost:6006/max/amount').then((res) => setData(res.json()));
  };

  useEffect(() => {
    getData();
  }, []);

  return (
    <div>
      <SubSample maxAmount={data?.maxAmount} />
    </div>
  );
};

오히려 SubSample component의 story를 생성해서 maxAmount 값을 argument로 받는 게 더 적절해 보입니다.

즉, "maxAmount가 달라지면서 영향을 받는 영역"이 꼭 mocking을 포함해야 하는지 한 번 더 생각해 봐야 한다는 것입니다. "maxAmount가 달라지면서 영향을 받는 영역"은 SubSample story를 생성해 보는 것만으로 충분합니다.

만약 이렇게 영역을 구분하지 않는다면 상위 컴포넌트 1개의 story에 여러 mocking을 넣는 방향으로 개발이 치우쳐집니다. 이유는 UI를 전체 구조에서 확인하는 게 편하고 mocking도 control에서 조작하기 쉬워졌기 때문입니다. 하지만 나중에 하위 컴포넌트에서 책임지고 있는 UI를 경우에 따라 확인하고 싶을 때는 story가 준비되어 있지 않은 경우를 맞닥뜨리게 됩니다.

MSW Storybook addon에서는 개발자가 다수의 story를 만드는데 불편함을 느끼고 있다는 것을 인지하고 있습니다. 하지만 아직 feature 개발이 되지 않은 것으로 보이며 개별 story로 생성하는 것이 권장됩니다.

결론

처음에 해당 로직을 찾아내서 프로젝트에 적용해보았을 때 개발 편의성이 올라갔습니다. 하지만 시간이 지나자 점점 상위 컴포넌트 story에 더 많은 mocking control을 붙이게 되었고 하위 컴포넌트 story에 소홀해졌습니다. 그리고 2~3개의 적은 story를 만드는 것보다 mocking control로 모두 해결하려 했습니다.

"storybook을 올바르게 사용하는가"에 대한 논의가 시작되었고 결과적으로 "첫번째 Sample 구조에서와 같이 필요한 경우에만 사용하며 두번째 Sample 구조에서는 SubSample을 story로 만들자"라고 결론이 났습니다.

참고

msw-storybook-addon open issue 1
msw-storybook-addon open issue 2
Mocking으로 생산성까지 챙기는 FE 개발 | Kakao Tech

0개의 댓글