storybook/testing-react 를 이용한 React 컴포넌트 중복 테스트 작성 방지
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 하나만을 사용하기엔 작성 시간에 비해 테스트할 수 있는 영역의 제한이 많아 늘 귀찮다는 이유로 피해왔는데, 둘을 적절하게 잘 연결해주는 것만으로도 훨씬 테스트를 작성하고자 하는 의욕이 살아났습니다. 혹시라도 중복되는 테스트 작성에 머리가 아프셨거나, 스토리북으로 이미 작성해두었으나 리팩토링 때마다 겁에 질리시는 분들이라면 꼭 한번 적용해보셨으면 좋겠습니다.