웹/React

storybook/testing-react 를 이용한 React 컴포넌트 중복 테스트 작성 방지

한땀코딩 2022. 4. 30. 11:03

Storybook?

Storybook은 컴포넌트 단위로 나뉜 UI를 프로그램과 별개로 띄워서 확인해볼 수 있게 해주는 일종의 테스트 도구입니다. 리액트 컴포넌트를 개발할 때, props나 상태 값에 따라 디자인부터 기능까지 달라질 수 있는데, 이를 매번 실제 개발하고 있는 페이지에 심어서 테스트하는 것은 번거롭고 놓치게 되는 부분이 많습니다. 제가 Storybook을 사용하면서 느꼈던 장점들은 이렇습니다.

  • 개발 중인 화면과 분리하여 UI를 확인 및 테스트 가능
  • props 값에 따라 달라지는 모든 형태를 기록해둘 수 있어 디자인 docs 작성 + 개발을 한번에 처리 가능
  • 디자이너와 협업 시 개발 상태를 공유 가능

Jest (React Testing Library)와의 차이

기존에 컴포넌트 테스트를 React Testing Library로 작성하시는 분들도 많은데, 그런 입장에서는 Storybook을 굳이 또 작성해야 해?라고 생각하시는 경우도 있을 거 같습니다. Jest를 사용함에도 굳이 제가 Storybook을 이용하는 데는 이런 이유가 있을 거 같습니다.

  • 시각적으로 디자인을 꼭 확인하기 위해 (내가 디자인을 하는 경우 😇 )
  • 브라우저 환경에서 동작을 확인하기 위해
  • 컴포넌트를 문서화하기 위해

그리고 이런 이유로 충분히 충족된다고 판단하여 최근까지는 개인적으로 Jest까지는 사용하지 않고 있었습니다. 그러나 이 필요성이 대두되는 순간은 리팩토링이나 페이지 단위 테스트를 작성할 때라고 생각합니다. 특히, 가장 하부의 atom이나 molecule 단위의 컴포넌트를 고치거나, state 로직을 고치는 경우 예상치 못한 곳에서 기능이 어긋나서 큰 오류로 이어지는 일도 있는 것 같습니다. 이를 위해 안정적인 CI를 위한 테스트 작성이 필요한데, Storybook은 이 갈증을 해결해주지는 못합니다.

storybook/testing-react addon 소개

그럼 결국 위의 문제를 모두 해결하려면 Storybook도 Jest도 작성해야 합니다. 실제로도 작년에 Jest를 붙여보려고 할 때, 눈으로 확인할 수 있고 이미 Storybook에 심어둔 코드를 번거롭게 또 작성한다는 느낌을 받아 구현에서 제외한 기억이 있습니다. 그러다 구독 중인 뉴스레터에서 소개한 storybook/testing-react가 이 불편함을 많은 부분 해소해주는 것을 알게되어 부착해보았습니다.

storybook/testing-react는 Storybook에 붙이는 addon으로, 기존 컴포넌트 대신 스토리북에서 props를 지정하여 생성해둔 스토리를 Jest에서 렌더하여 확인할 수 있게 해 줍니다. 이렇게 하면 Jest에서 또 props를 따로 정의할 필요 없이 unit test를 작성할 수 있습니다.

storybook/testing-react 사용 방법

이번 글에서는 공식 문서를 참조하여 간단한 Button 컴포넌트를 생성하여 테스트를 작성해보겠습니다. 기본적인 환경은 아래를 가정합니다.

  • React 기반의 프로젝트
  • Storybook(6버전 이상)
  • Jest 설치 완료

설치

먼저 라이브러리를 설치해줍니다.

npm install --save-dev @storybook/testing-react

컴포넌트 및 스토리 생성

버튼의 내용물은 children으로 받아오는 Button 컴포넌트를 생성하는 코드입니다.

export interface ButtonProps
  extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
  children: string | React.ReactNode
  onClick: () => void
}

const Button: React.FC<ButtonProps> = ({ children, onClick }: ButtonProps) => {
  return <button onClick={onClick}>{children}</button>
}

export default Button

그리고 이를 화면에서 테스트하기 위해 스토리를 다음과 같이 생성합니다.

import { Meta, Story } from '@storybook/react/types-6-0'
import styled from 'styled-components'

import Button, { ButtonProps } from '@/components/atoms/buttons/Button'

export default {
  title: 'Atoms/buttons/Button',
  component: Button,
} as Meta

const TextButton: Story<ButtonProps> = (args) => {
  return (
    <>
      <Button {...args}>버튼입니다</Button>
    </>
  )
}

export const Text = TextButton.bind({})

여기서 살펴보면, TextButton 스토리는 버튼입니다라는 텍스트를 이용한 Button을 만들어내고 있습니다. 그렇다면 Jest로 unit test를 작성할 때, Button 컴포넌트를 불러와서 또 버튼입니다를 주입할 필요 없이, TextButton 스토리를 바로 렌더 하여 다음의 내용을 확인해보면 됩니다.

  • 화면에 버튼입니다 라는 텍스트를 가진 button 태그가 생성되었는가?
  • 버튼을 클릭하면 이벤트가 발생하는가?

(물론 후자의 경우, button 태그 자체에 내제된 기능이어서 제 경우에서는 스킵하는 경우도 많습니다)

테스트 코드 작성

/**
 * @jest-environment jsdom
 */

import { composeStories } from '@storybook/testing-react'
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'

import * as ButtonStories from '../Button.stories'
import * as TextButtonStories from '../TextButton.stories'

const { Text: TextButton } = composeStories(ButtonStories)

describe('<TextButton />', () => {
  test('<button> 태그가 렌더링된다', () => {
    const { container } = render(<TextButton />)
    const buttons = container.querySelectorAll('button')
    expect(buttons).toHaveLength(1)
  })

  test('버튼의 내용이 렌더링된다', () => {
    render(<TextButton />)
    expect(screen.getByText('다운로드')).not.toBeNull()
  })

  test('클릭 시 이벤트가 발생한다', () => {
    const onClickSpy = jest.fn()
    render(<TextButton onClick={onClickSpy}></TextButton>)
    fireEvent.click(screen.getByRole('button'))
    expect(onClickSpy).toHaveBeenCalled()
  })
})

storybook/testing-react에서 제공하는 composeStories를 이용하면 스토리 생성 시 넘겨주었던 args, decorator를 모두 포함하여 렌더 할 수 있도록 오브젝트를 반환해줍니다. 이후 screen, render 등의 메서드에 반환된 오브젝트를 넘겨주어 바로 테스트해볼 수 있습니다.

마치며

더욱 상세한 사용법은 Storybook의 공식 문서와 React Testing Library를 통해 알아볼 수 있습니다. 이번 글에서는 자세한 테스트 작성법보다는, 테스트 중복 작성을 방지할 수 있는 좋은 라이브러리를 소개하고자 했기 때문에 간단한 사용법만 적어보았습니다. 제 경우에는 스토리북이나 Jest 하나만을 사용하기엔 작성 시간에 비해 테스트할 수 있는 영역의 제한이 많아 늘 귀찮다는 이유로 피해왔는데, 둘을 적절하게 잘 연결해주는 것만으로도 훨씬 테스트를 작성하고자 하는 의욕이 살아났습니다. 혹시라도 중복되는 테스트 작성에 머리가 아프셨거나, 스토리북으로 이미 작성해두었으나 리팩토링 때마다 겁에 질리시는 분들이라면 꼭 한번 적용해보셨으면 좋겠습니다.

' > React' 카테고리의 다른 글

CSR vs SSR (feat. Next.js)  (1) 2021.03.07
상태관리 맛보기  (0) 2020.11.29