StoryBook 연습하기!

9rganizedChaos·2021년 10월 29일
0
post-thumbnail
post-custom-banner

시작하기

🔧 리액트 타입스크립트 앱 설치!

npx create-react-app storybook-practice --template typescript
npm run start

위 cli 명령어를 입력해 우선 스토리북을 실습할 디렉토리를 생성해주었다.
npm run start를 통해 로컬 호스트에서 앱이 잘 실행되는 것을 확인!

⚙️ 스토리북 설치 및 초기화!

스토리북 공식문서에서 안내해주는 바와 같이 스토리북을 설치하고 초기화한다.
스토리북 공식문서는 무려 React를 위한 튜토리얼을 따로 제공한다.

# Add Storybook:
npx -p @storybook/cli sb init

설치를 완료하고 나면 package.json devDependencies에서 위와 같은 모습을 확인할 수 있다.
설치가 잘 되었는지 확인하기 위해 아래 명령어들을 cli에 입력해보자!

# Run the test runner (Jest) in a terminal:
yarn test --watchAll

# Start the component explorer on port 6006:
yarn storybook

🎛 CSS and Asset(font and Icon) Setting

src/index.css공식문서가 미리 마련해둔 CSS setting을 그대로 옮기고, 아래 명령어를 통해서 폰트와 아이콘도 설정해준다.

npx degit chromaui/learnstorybook-code/src/assets/font src/assets/font
npx degit chromaui/learnstorybook-code/src/assets/icon src/assets/icon



간단한 컴포넌트

CDD 방법론에 따라 UI를 개발할 것이므로 간단한 컴포넌트를 미리 제작한다.
사실 리액트는 컴포넌트 기반 개발이 default라고 처음부터 배웠으나, 지금까지는 늘 페이지부터 개발하고, 큰 컴포넌트부터 작은 컴포넌트로 개발해 나갔던 것이 사실이다. 이 기회에 제대로 작은 컴포넌트 부터 빌드업해 나가는 개발 방식에 익숙해지자!

Task 컴포넌트 제작!

스토리북은 기본적으로 컴포넌트와 그 하위 스토리! 이렇게 두 가지 기본 단계로 구성되어 있다.

이와 같은 형태인데, 각 스토리가 해당 컴포넌트에 대응되는 것이다.
물론 스토리는 꼭 하나일 필요는 없다. 컴포넌트 별로 원하는 만큼의 스토리 파일을 생성해 사용할 수 있다.

export default {
  component: Task, // 이 스토리 파일이 종속된 컴포넌트
  title: "Task", // 스토리북 앱의 사이드바에서 컴포넌트를 참조할 이름
};

스토리북에게 우리가 문서화하는 컴포넌트를 알려주기 위해 위와 같은 default export를 생성해주어야 한다. 우리는 하나의 컴포넌트에 여러 가지 스토리를 넘겨주면서 테스트를 할 것이기 떄문에 아래와 같이 하나의 Template 변수를 만들어주고, 해당 패턴을 활용해 스토리에 도입할 것이다.

const Template = (args) => <Task {...args} />;
// 먼저 하나의 템플릿을 만들어준다.

export const Default = Template.bind({});
// 위와 같은 방식을 사용해 함수의 복사본을 생성한다.
Default.args = {
  task: {
    id: "1",
    title: "Test Task",
    state: "TASK_INBOX",
    updatedAt: new Date(2018, 0, 1, 9, 0),
  },
};

export const Pinned = Template.bind({});
Pinned.args = {
  task: {
    ...Default.args.task,
    state: "TASK_PINNED",
  },
};

export const Archived = Template.bind({});
Archived.args = {
  task: {
    ...Default.args.task,
    state: "TASK_ARCHIVED",
  },
};

구성하기

.storybook/main.js 파일을 다음과 같이 수정한다.

module.exports = {
  stories: ['../src/components/**/*.stories.js'],
  // 우리가 작성할 스토리 파일들의 위치!
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/preset-create-react-app',
  ],
};

preview.js도 다음과 같이 수정하여준다.

import '../src/index.css'; //👈 The app's CSS file goes here

//👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI.
export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
};

매개변수는 스토리북의 기능과 애드온 동작을 제어하기 위해 사용된다.
우리의 경우 이를 활용하여 action이 처리되는 방식을 구성할 것이다.
actions는 클릭되었을 때 스토리북 UI의 actions 패널에 나타날 콜백을 생성할 수 있도록 해준다.

이제 스토리북 서버를 재시작해보자.

이제 스토리북에 세 가지 테스트 케이스가 생성된 것을 확인해볼 수 있다.

테이터 요구사항 명시하기

React에서 propTypes를 활용해 데이터의 요구사항을 명하시여준다.

import PropTypes from 'prop-types';

Task.propTypes = {
  /** Composition of the task */
  task: PropTypes.shape({
    /** Id of the task */
    id: PropTypes.string.isRequired,
    /** Title of the task */
    title: PropTypes.string.isRequired,
    /** Current state of the task */
    state: PropTypes.string.isRequired,
  }),
  /** Event to change the task to archived */
  onArchiveTask: PropTypes.func,
  /** Event to change the task to pinned */
  onPinTask: PropTypes.func,
};

이렇게 데이터 요구사항까지 정해부고나면,
이제 서버나 프론트엔드 앱 전체를 실행하지 않고도 컴포넌트 단위의 개발이 가능해진다는 걸 알 수 있다.
그동안은 늘 props를 내려줘야만 하위컴포넌트를 비로소 제대로 개발할 수 있다고 생각했었다.
mockData를 만드는 것이 좀 귀찮기도 했었고, 그러나 이러한 방식이라면 충분히 컴포넌트 드리븐 개발이 가능할 것이다.

테스트 자동화

현재로서는 직접 입력해준 story들을 확인하는 것까지만 가능하다.
이를 직접 test를 돌릴 수 있도록 StoryShots Addon을 설치해준다.

yarn add -D @storybook/addon-storyshots react-test-renderer

그리고 테스트파일을 생성해주면 yarn test로 테스트를 실행할 수 있게 된다!



복합적 컴포넌트 조합하기

TaskList 만들기

위에서 만들었던 Task 컴포넌트를 조합해 TaskList를 만들 수 있다.
우리는 다음의 네 가지 상황에 대응할 수 있는 TaskList 스토리를 만들어줄 것이다.

  • 일반 TaskList
  • Pinned로 강조된 task가 존재하는 TaskList
  • task가 없는 상태
  • 로딩 중인 TaskList

설정하기

위에서 했던 것과 같이 TaskList.jsTaskList.stories.js를 각각 생성해준다.

// src/components/TaskList.stories.js

export default {
  component: TaskList,
  title: 'TaskList',
  decorators: [story => <div style={{ padding: '3rem' }}>{story()}</div>],
};

데코레이터는 스토리에 임의의 래퍼를 제공해주는 방법.
또한 데코레이터는 providers에서 스토리를 감싸줄 때 사용될 수 있다.

설정을 마치면 스토리북에서 위와 같은 화면을 확인할 수가 있다!

컴포넌트 구현하기

뼈대만 잡아놓은 컴포넌트 구조를 디테일하게 잡아준다. (상세 내용은 공식문서 참고)

Jest를 활용한 단위 테스트!

Task에서는 렌더링이 잘 되는지 확인하는 것 이상의 많은 복잡성이 필요하지는 않았다.
TaskList에서는 복잡성이 더해지기 떄문에 Jest를 활용해 단위테스트를 도입하도록 한다.

import React from "react";
import ReactDOM from "react-dom";
import "@testing-library/jest-dom/extend-expect";

import { WithPinnedTasks } from "./TaskList.stories"; //👈  Our story imported here

it("renders pinned tasks at the start of the list", () => {
  const div = document.createElement("div");
  //👇 Story's args used with our test
  ReactDOM.render(<WithPinnedTasks {...WithPinnedTasks.args} />, div);

  // We expect the task titled "Task 6 (pinned)" to be rendered first, not at the end
  const lastTaskInput = div.querySelector(
    '.list-item:nth-child(1) input[value="Task 6 (pinned)"]'
  );
  expect(lastTaskInput).not.toBe(null);

  ReactDOM.unmountComponentAtNode(div);
});

이제 각 유닛별 테스트가 가능해졌다!



profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!
post-custom-banner

0개의 댓글