스토리북
디자이너나 프로젝트 리더와 구현된 UI 컴포넌트의 공유가 가능해지면 협업시 능률이 높아진다.
UI 컴포넌트 탐색기는 구현된 UI 컴포넌트를 쉽게 공유할 수 있도록 도와주는 협업 도구
스토리북의 UI 컴포넌트 테스트는 통합 테스트, E2E 테스트 중간에 위치한 테스트
스토리북은 UI 컴포넌트 탐색기지만 테스트 기능도 강화했음
설치
Copy npx storybook init
> webpack 5 //or vite 번들러 설정
> y //샘플 코드 설치할지
> react // 어떤 라이브러리의 샘플 코드를 선택할지
> npm run storybook
스토리
CSF3.0은 객체를 개별적으로 export 하여 스토리 등록함
Copy import { Button } from './Button' ;
export default {
title : "Example/Button" ,
component : Button ,
}
// 다른 이름으로 export 하게 되면 다른 스토리로 등록됨
export const Default = {
args : {
label : "Button" ,
} ,
}
모든 스토리에는 Global
, Component
, Story
라는 세 단계의 설정이 깊은 병합 방식으로 적용됨
Global < Component < Story 순으로 설정의 우선순위가 높아진다.
공통으로 적용할 항목을 적절한 스코프에 설정하여 스토리마다 개별적으로 설정해야하는 작업을 최소화
Global 단계 : 모든 스토리에 적용할 설정 (.storybook/preview.ts)
Component 단계 : 스토리 파일에 적용할 설정 (export default)
Story 단계 : 개별 스토리에 적용할 설정 (export const)
에드온
스토리북을 설치할 때 기본적으로 추가되는 @storybook/addon-essentials
은 필수 에드온
Controls를 활용한 디버깅
Props에 전달된 값에 따라 컴포넌트는 다른 스타일과 기능을 제공한다.
스토리북은 Props를 변경해 컴포넌트가 어떻게 표시되는지 실시간으로 디버깅 가능
@Storybook/addon-controls
라는 에드온,
@storybook/addon-essentials
에 포함되어있음
Actions를 활용한 이벤트 핸들러 검증
이벤트 핸들러가 어떻게 호출됐는지 로그를 출력하는 기능
@storybook/addon-actions
마찬가지로 @storybook/addon-essentials
에 포함되어있음
Global 단계 설정인 .storybook/preview.js
를 보면 argTypesRegex: "^on[A-Z].*"
라는 설정이 on
으로 시작하는 모든 이벤트 핸들러는 자동적으로 Actions
패널에 로그를 출력하게 된다.
⚠️ 만약 프로젝트에 이벤트 핸들러의 이름으로 사용하는 다른 네이밍 컨벤션이 있다면 해당 컨벤션에 따라 정규표현식을 수정해야 한다.
Copy export const parameters = {
actions : { argTypesRegex : "^on[A-Z].*" } ,
}
반응형 대응을 위한 뷰포트 설정
반응형으로 구현된 UI 컴포넌트는 화면 크기별로 스토리를 등록할 수 있다.
@storybook/addon-viewport
에서 지원
SP(스마트폰) 레이아웃으로 스토리를 등록하려면 parameters.viewport
를 설정해야 한다.
Copy import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport' ;
export const SPStory = {
parameters : {
viewport : {
viewports : INITIAL_VIEWPORTS ,
defaultViewport : "iphone6"
} ,
screenshot : {
viewport : {
width : 375 ,
height : 667 ,
deviceScaleFactor : 1 ,
} ,
fullPage : false ,
}
}
}
Copy // SPStory: SP 레이아웃용 커스텀 공통 설정 모듈
import { SPStory } from '@/tests/storybook' ;
export const SPLoggedIn : Story = {
parameters : {
... SPStory .parameters ,
}
}
Context API에 의존하는 스토리 등록
리액트의 Context API
에 의존하는 스토리에는 스토리북의 데코레이터(decorator) 를 활용
초깃값을 주입할 수 있도록 Provider
를 만들면 Context
의 상태에 의존하는 UI를 간단하게 재현 가능
스토리북의 데코레이터
데코레이터란 스토리의 렌더링 함수에 적용할 래퍼(wrapper)
Copy import { ChildComponent } from "./" ;
export default { // 컴포넌트 단계의 설정
title : "ChildComponent" ,
component : ChildComponent ,
decorators : [
(Story) => (
< div style = {{ padding : "60px" }} > // 해당 파일의 스토리들이 모두 적용됨
< Story /> // 각 스토리가 전개됨
</ div >
)
]
}
로그인한 사용자 정보가 있는 Provider(LoginUserInfoProvider)
를 데코레이터가 소유했다면 Context
의 Provider
에 의존하는 UI 컴포넌ㅌ의 스토리에서도 로그인한 사용자의 정보를 표시할 수 있다.
Copy import { LoginUserInfoProvider } from "@/components/providers/LoginUserInfo"
import { Args , PartialStoryFn } from '@storybook/csf' ;
import { ReactFramework } from '@storybook/react' ;
export const LoginUserInfoProviderDecorator = (
Story : PartialStoryFn < ReactFramework , Args >
) => (
< LoginUserInfoProvider >
< Story /> // 스토리가 Context를 통해 LoginUserInfo를 참조
</ LoginUserInfoProvider >
)
유사하게 공통 레이아웃을 제공할 데코레이터를 만들면 상황에 따라 사용할 수 있다.
앱에서 필요한 Provider
라면 실제 구현 코드와 똑같이 사용해도 상관없지만, 이 밖에는 스토리북 전용 Provider를 데코레이터로 만드는것이 좋다.
Copy import { BasicLayout } from "@/components/layouts/BasicLayout"
import { Args , PartialStoryFn } from '@storybook/csf' ;
import { ReactFramework } from '@storybook/react' ;
export const BasicLayoutDecorator = (
Story : PartialStoryFn < ReactFramework , Args >
) => BasicLayout (< Story />)
데코레이터 고차함수
데코레이터를 만드는 함수(고차 함수)를 작성하면 쉽게 데코레이터 작성 가능
example
Provider Context
는 { message, style }
라는 상태를 소유하는데, 이 상태를 통해 정보를 통지
각 스토리는 createDecorator
라는 고차 함수를 사용해 설정을 최소화
Copy // 사용 예시
export const Succeed : Story = {
decorators : [ createDecorator ({ message : "성공했습니다." , style : "succeed" })] ,
};
export const Failed : Story = {
decorators : [ createDecorator ({ message : "실패했습니다." , style : "failled" })] ,
}
export const Busy : Story = {
decorators : [ createDecorator ({ message : "통신 중." , style : "busy" })] ,
}
<ToastProvider>
가 제공하는 정보를 <Toast>
컴포너트가 표시
이와 같이 고차함수를 만들면 초기값(defaultState
)를 쉽게 주입 가능
Copy import { ToastProvider , ToastState } from '@/components/providers/ToastProvider' ;
import { ComponentMeta , ComponentStoryObj } from '@storybook/react' ;
import { Toast } from '.' ;
function createDecorator (defaultState ?: Partial < ToastState >) {
return function Decorator () {
return (
< ToastProvider defaultState = {{ ... defaultState , isShown : true }} >
{null}
</ ToastProvider >
)
}
}
웹 API에 의존하는 스토리 등록
웹 API에 의존하는 컴포넌트는 스토리에도 웹 API가 필요함
컴포넌트를 렌더링하려면 웹 API 서버를 실행하고 있어야 한다.
스토리북을 빌드해서 정적 사이트로 호스팅할 대도 서버 상태가 좋지 않다면 API 요청이 원활히 이뤄지지 않게됨
이와 같은 UI 컴포넌트는 MSW
를 사용해야 한다.
에드온 설정
Copy npm i msw msw-storybook-addon --save-dev
스토리북에서 MSW를 사용하려면 msw
와 msw-storybook-addon
을 설치해야 한다.
.storybook/preview.js
에서 initialize
함수를 실행해 MSW를 활성화
mswDecorator
는 모든 스토리에 필요하므로 글로벌 단계에서 설정
Copy // .storybook/preview.js
import { initialize , mswDecorator } from 'msw-storybook-addon' ;
export const decorators = [mswDecorator];
initialize ();
프로젝트에 처음 MSW를 설치한다면 public/
경로를 다음의 커맨드로 실행
(<PUBLIC_DIR>
을 프로젝트의 public 디렉토리 명으로 변경)
커맨드를 실행하면 mockServiceWorkjer.js
가 생성되며 커밋해야함
Copy npx msw init < PUBLIC_DI R >
Copy // .storybook/main.js
module . exports = {
//...
staticdirs : [ "../public" ] ,
}
요청 핸들러 변경
다른 parameters
와 동일하게 Global, Component, Story 설정을 통해 스토리에 사용할 요청 핸들러가 결정됨
.storybook/preview.js
에 필요한 설정을추가
예를 들어 모든 스토리에 로그인한 사용자 정보가 필요하다면 로그인한 사용자 정보를 반환하는 MSW
핸들러를 Global 단계에 설정하는 것이 좋다.
Copy export const parameters = {
// 기타 설정 생략
msw : {
handlers : [
rest .get ( "/api/my/profile" , async (_ , res , ctx) => {
return res (
ctx .status ( 200 ) ,
ctx .json ({
id : 1 ,
name : "EonsuBae" ,
bio : "..."
likeCount: 1 ,
//...
})
)
}
]
}
}
요청 핸들러가 스토리에 적용되는 우선순위는 Story > Component > Global
요청 핸들러는 스토리마다 독립적으로 설정할 수 있기 때문에 같은 컴포넌트에서 웹 API 응답에 따라 다른 내용 표시 쌉가능
오류 응답 상태 코드에 따라 다른 내용이 표시되는 상황도 검증 가능
고차 함수로 요청 핸들러 리팩터링
웹 API에서 URL과 응답 내용은 불가분의 관계
스토리나 테스트에 URL을 하드코딩하면 URL 사양이 변경돼도 변경된 사양이 반영되지 않게됨
이를 피하려면 웹 API 클라이언트 설정에 있는 고차 함수를 통해 만든 요청 핸들러에 URL을 정의해야함
handleGetMyProfile
는 따로 정의한 msw 요청 핸들러
클라이언트 설정 부분에서 요청 핸들러와, 고차 함수(데코레이터)를 제데로 활용하면 코드가 간결해짐
Copy import { rest } from "msw" ;
import { path } from ".." ;
import { getMyProfileData } from "./fixture" ;
export function handleGetMyProfile (args ?: {
mock ?: jest . Mock < any , any >;
status ?: number ;
}) {
return rest .get ( path () , async (_ , res , ctx) => {
args ?.mock?. ();
if ( args ?.status) {
return res ( ctx .status ( args .status));
}
return res ( ctx .status ( 200 ) , ctx .json (getMyProfileData));
});
}
export const handlers = [ handleGetMyProfile ()];
Copy export const NotLoggedIn : Story = {
parameters : {
msw : { handlers : [ handleGetMyProfile ({ status : 401 })] }
}
}
Copy // 로그인 화면의 스토리 파일
export default {
component : Login ,
parameters : {
nextRouter : { pathname : "/login" } ,
msw : { handlers : [ handleGetMyProfile ({ status : 401 })] } ,
} ,
decorators : [BasicLayoutDecorator] ,
} as ComponentMeta < typeof Login>;
Next.js Router에 의존하는 스토리
컴포넌트 중 특정 URL에서만 사용할 수 있는 컴포넌트가 존재함
이와같은 컴포넌트의 스토리를 등록하려면 에드온이 필요
storybook-addon-next-router
에드온을 추가하면 Router
상태를 스토리마다 설정 가능해짐
에드온 설정
.storybook/main.js
와 .storybook/preview.js
에 설정을 추가
Copy // .storybook/main.js
module . exports = {
//...
stories : [ "../src/**/*.stories.@(js|jsx|ts|tsx)" ] ,
addons : [ "storybook-addon-next-router" ]
}
Copy // .storybook/preview.js
import { RouterContext } from "@next/dist/shared/lib/router-context" ;
export const parameters = {
//...
nextRouter : {
Provider : RouterContext .Provider
}
}
Router에 의존하는 스토리 등록
헤더의 네비게이션에는 pathname(브라우저 URL)
에 따라 메뉴 하단의 스타일이 달라진다고 해보자
Copy export const RouteMyPosts : Story = {
parameters : {
nextRouter : { pathname : "/my/posts" } ,
}
}
export const RouterMyPostsCreate : Story = {
parameters : {
nextRouter : { pathname : "my/posts/create" } ,
}
}
Play function을 활용한 인터렉션 테스트
props
를 전달해 다양한 상황을 재현 가능하지만 UI에 인터렉션을 할당하는 방법으로도 재현하는 상황도 존재
폼에서 값을 전송하기 전 브라우저에 입력된 내용에 유효성 검사를 실시해 문제가 있으면 오류를 표시하는 상황
문자 입력, 포커스 아웃 이벤트, 전송 버튼 클릭 등 과 같은 인터렉션이 필요함
스토리북 기능인 play function
를 사용하면 인터렉션 할당 상태를 스토리로 등록 가능
에드온 설정
Copy $ npm i @storybook/testing-library @storybook/jest @storybook/addon-interactions --save-dev
@sotrybook/testing-library
, @storybook/jest
, @storybook/addon-interactions
Copy // .storybook/main.js
module . exports = {
// 다른 설정은 생략
stories : [ "../src/**/*.stories.@(js|jsx|ts|tsx)" ] ,
addons : [ "@storybook/addon-interactions" ] ,
features : {
interactionsDebugger : true ,
}
}
인터렉션 할당
인터렉션을 할당하기 위해 스토리에 play
함수를 설정
테스팅 라이브러리 및 jsdom
를 사용할 때와 동일하게 userEvent
를 사용해서 컴포넌트에 인터렉션 할당
테스팅 라이브러리에서 사용하는 getBy
, userEvent
거의 동일한 API라 테스트를 작성하는 느낌으로 작성
스토리가 렌더링될 때 play function
이 자동으로 실행돼 문자가 입력된 것을 확인 가능
인터렉션이 제데로 할당하지 않으면 도중에 인터렉션이 중단됨
Copy export const SucceedSaveAsDraft : Story = {
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
await user .type (
canvas .getByRole ( "textbox" , { name : "제목" }) ,
"나의 기사"
)
}
}
단언문 작성 (테스트 검증)
@storybook/jest
의 expect
함수를 사용하면 컴포넌트에 인터렉션을 할당한 상태에서 단언문 작성가능
Copy export const SavePublish : Story = {
play : async ({ canvasElement }) => {
const canavs = within (canvasElement);
await user .type (
canvas .getByRole ( "textbox" , { name : "제목" }) ,
"나의 기사"
);
await user .click ( canvas .getByRole ( "switch" , { name : "공개 여부" }));
await expect (
canvas .getByRole ( "button" , { name : "공개하기" })
) .toBeInTheDocument ()
}
}
Copy // 아무것도 입력하지 않고 비공개 상태로 저장을 시도하면 유효성 검사가 실패해 오류가 표시됨
export const FailedSaveAsDraft : Story = {
play : async ({ canvasElement }) => {
const canavs = within (canvasElement);
await user .click ( canvas .getByRole ( "switch" , { name : "비공개 상태로 저장" }));
const textbox = canvas .getByRole ( "textbox" , { name : "제목" });
await waitFor (() =>
expect (textbox) .toHaveErrorMessage ( "한 글자 이상의 문자를 입력하세요" )
);
}
}
addon-a11y를 활용한 접근성 테스트
스토리북은 컴포넌트 단윙로 접근성을 검증하는 용도로도 활용됨
스토리북을 확인하면서 코드를 작성하면 접근성에 문제가 있는지 조기에 발견 가능
에드온 설정
@storybook/addon-a11y
에드온을 추가하면 스토리북 탐색기에서 접근성 관련 우려사항을 알려줌
parameters.a11y에 해당 에드온 설정을 사용
다른 parameters
와 동일하게 Global > Compeonnt > Story 단계로 나눠 적용 가능
Copy // .storybook/main.js
module . exports = {
// 다른 설정 생략
stories : [ "../src/**/*.stories.@(js|jsx|ts|tsx)" ] ,
addons : [ "@storybook/addon-a11y" ] ,
}
접근성과 관련한 주의 사항 점검
에드온 패널에 추가된 Accessibility 패널을 열어보면 검증한 내용이 Violations, Passes, Incomplete로 구분됌
Highlight results
를 체크하면 주의점이 점선으로 표시되며 강조됨
일부 규칙 위반을 무효화하기
규칙이 지나치게 엄격하다고 느끼거나 원하지 않는 보고 내용이 있는 경우 일부 내용을 무효화 할 수 있다.
무효화 기능도 마찬가지로 Global, Compoennt, Story 나눠 설정 가능
rules
에 있는 id
(여기선 "label"
)은 axe-core의 Rule Descriptions 을 참고해 적용
해당 에드온은 접근성 검증도구로 axe 를 사용한다.
parameters
등의 세부 설정은 axe-core의 공식문서를 참고하자.
Copy export default {
component : Switch ,
parameters : {
a11y : {
config : { rules : [{. id : "label" , enabled : false }] }
}
}
} as ComponentMeta < typeof Switch>;
접근성 검증 생략
접근성 검증 자체를 생략하고 싶다면 parameters.a11y.disable
를 true
로 설정
일부 규칙 위반 무효화와 달리 접근성 자체를 검증 대상에서 제외시키므로 신중히 사용
Copy export default {
component : Switch ,
parameters : {
a11y : { disable : true }
}
} as ComponentMeta < typeof Switch>;
스토리북 테스트 러너
스토리북의 테스트 러너 는 스토리를 실행가능한 테스트로 변환한다.
테스트로 변환된 스토리는 제스트와 플레이라이트에서 실행된다.
이 기능을 활용해서 스토리북에 스모크 테스트는 물론 play function
을 활용한 인터렉션 테스트, 접근성 테스트 등을 할 수 있어 UI 컴포넌트 테스트로도 활용 가능
스모크 테스트 : 제품의 기본적인 작동 여부를 대략적으로 검증하는 테스트를 말함
테스트 러너를 활용한 테스트 자동화
컴포넌트가 변경되면 등록된 스토리에도 변경 사항을 반영해야 한다.
예를 들면 props
가 변경되거나 의존하는 웹 API의 데이터가 변경됐다면 스토리에도 변경사항을 반영해야 오류가 발생하지 않게됨
@storybook/test-runner
를 사용해서 명령줄 인터페이스나 CI에서 테스트 러너를 실행하면 등록된 스토리에 오류가 없는지 검증할 수 있다.
Copy // package.json
{
"scripts" : {
"test:storybook" : "test-storybook"
}
}
테스트 러너를 활용한 play function 테스트 자동화
play function
을 사용하는 스토리에 컴포넌트의 변경사항을 반영하지 않으면 도중에 인터렉션이 실패됨
테스트 러너는 play function
을 사용하는 스토리를 대상으로 인터렉션 오류 없이 정상적으로 종료됐는지 검증한다.
복잡한 인터렉션이 있는 경우에도 육안으로 확인하면서 테스트할 수 있기 떄문에 테스팅 라이브러리와 jest-dom
을 사용할 때보다 편하게 테스트 코드를 작성할 수 있다. (스토리북의 장점)
Copy // 모바일 한정으로 드로어 메뉴를 렌더링하고, 스토리에는 버튼을 클릭해서 드로어 메뉴를 여는 인터렉션이 등록됨
export const SPLoggedInOpenedMenu : Story = {
storyName : "Sp 레이아웃에서 드로어 메뉴를 연다" ,
parameters : {
... SPStory .parameters ,
screenshot : {
... SPStory . parameters .screenshot ,
delay : 200 ,
}
} ,
play : async ({ canvasElement }) => {
const canvas = within (canvasElement);
const button = await canvas .findByRole ( "button" , {
name : "메뉴 열기" ,
});
await user .click (button);
const navigation = canvas .getByRole ( "navigation" , {
name : "내비게이션"
});
await expect (navigation) .toBeInTheDocument ();
}
}
뷰포트 설정이 반영되지 않는 문제 (이미 해결됨)
Copy module . exports = {
async preRender (page , context) {
if ( context . name .startsWith ( "SP" )) {
// SP로 시작하는 스토리의 뷰포트를 스마트폰 사이즈로 고정
page .setViewportSize ({ width : 375 , height : 667 });
} else { // 그외 모든 스토리의 뷰포트를 PC 사이즈로 고정
page .setviewportSize ({ width : 1280 , height : 800 });
}
}
}
테스트 러너를 활용한 접근성 테스트 자동화
스토리북의 테스트 러너는 플레이라이트와 헤드리스 브라우저에서 실행된다.
덕분에 플레이라이트의 생태계에 있는 기능들을 테스트 러너에서 활용할 수 있다.
axe-playwright
는 접근성 검증 도구인 axe
를 사용하는 라이브러리로서 접근성 관련 문제점을 찾음
Copy $ npm i axe-playwright --save-dev
기본 설정을 사용하면 Incomplete
도 오류로 검출된다.
만약 오류가 너무 많다면 includedImpacts
에 critical
만 설정해서 Violations
에 해당하는 오류만 검출되도록 변경 가능
오류 검출 수준을 조정하면서 단계적으로 개선을 시도할 수 있다.
Copy // .storybook/test-runner.js
const { getStoryContext } = require ( "@storybook/test-runner" );
const { injectAxe , checkA11y , configureAxe } = require ( "axe-playwright" );
module . exports = {
async preRender (page , context) {
if ( context . name .startsWith ( "SP" )) {
page .setViewportSize ({ width : 375 , height : 667 });
} else {
page .setviewportSize ({ width : 1280 , height : 800 });
}
await injectAxe (page); // axe를 사용하는 검증 설정
} ,
async postRender (page , context) {
const storyContext = await getStoryContext (page , context);
if ( storyContext . parameters ?. a11y ?.disable) {
return ;
}
await configureAxe (page , {
rules : storyContext . parameters ?. a11y ?. config ?.rules ,
});
await checkA11y (page , '#root' , { // axe를 사용한 검증
includedImpacts : [ "critical" , "serious" ] , // Vioations에 해당하는 오류만 검출
detailedReport : false ,
detailedReportOptions : { html : true } ,
axeOptions : storyContext . parameters ?. a11y ?.options ,
})
}
}
스토리를 통합 테스트에 재사용하기
제스트로 작성한 테스트 코드와 더불어 스토리까지 커밋해야 한다면 운용비가 너무 많이 증가하는것 아닌가 염려됨
스토리를 통합 테스트에 재사용해서 해결할 수 있다.
스토리 재사용
컴포넌트 테스트는 검증을 시작하기 전에 상태를 만들어야 한다.
상태를 만드는 일과 스토리를 만드는 일은 거의 동일한 작업
Copy // 스토리 등록용 함수
function createDecorator (defaultState ?: Partial < AlertDialogState >) {
return function Decorator (Story : PartialStroyFn < ReactFramework , Args >) {
return (
< AlertDialogProvider defaultState = {{ ... defaultState , isShown : true }}>
< Story />
</ AlertDialogProvider >
)
}
}
// 실제로 등록할 스토리
export const Default : Story = {
decorators : { createDecorator ({ message: "성공했습니다." })] ,
}
export const CustomButtonLabel: Story = {
decorators : {
createDecorator ({
message: "기사를 공개합니다. 진행하시겠습니까?"
cancleButtonLabel: "CANCEL" ,
okButtonLabel: "OK" ,
})
] ,
}
export const ExcludeCancel: Story = {
decorators : {
createDecorator ({
message: "전송됐습니다"
cancleButtonLabel: undefined ,
okButtonLabel: "OK" ,
})
] ,
}
해당 컴포넌트는 AlertDialogProvider
에 의존하지만 createDecorator
를 사용하면 초기값을 주입할 수 있어 다양한 스토리를 등록할 수 있다.
Context API에 의존하는 컴포넌트, 즉 AlertDialogProvider
가 없으면 작동하지 않는다.
테스트의 render
에 매번 AlertDialogProvider
를 추가해야 한다.
매번 이를 추가하는 것이 언급한 상태를 미리 만들어 두는 작업 에 해당한다.
스토리를 등록할 때 이미 createDecorator
함수로 상태를 만들었지만 테스트 코드에도 같은 작업을 반복한다면 중복 작업이라고 느낄 수 있다.
스토리를 테스트 대상으로 만들어 재활용 하면 컴포넌트 테스트를 위한 상태를 재차 정의하지 않아도 된다.
스토리를 import하여 테스트 대상으로 만들기
테스트에 스토리를 재사용하기 위해 import
하려면 전용 라이브러리인 @storybook/testing-react
를 사용해야 한다.
스토리 파일의 내용을 불러온 후 composeStories(stories)
로 각 스토리를 선언하면 테스트 준비 완료
단언문은 스토리를 render
한 후 작성할 수 있기 때문에 스토리를 테스트의 일부라고 생각하자.
Copy import { composeStories } from "@storybook/tresting-react" ;
import { render , screen } from "@testinb-library/react" ;
import * as stories from "./index.stories" ; // jest를 사용하는 테스트에 스토리 파일 불러오기
const { Default , CustomButtonLabel , ExcludeCancel } = composeStories (stories);
describe ( "AlertDialog" , () => {
test ( "Default" , () => {
render (< Default />); // 스토리 렌더링
expect ( screen .getByRole ( "alertDialog" )) .toBeInTheDocument ();
})
test ( "CustomButtonLabel" , () => {
render (< CustomButtonLabel />); // 스토리 렌더링
expect ( screen .getByRole ( "button" , { name : "OK" })) .toBeInTheDocument ();
expect ( screen .getByRole ( "button" , { name : "CANCEL" })) .toBeInTheDocument ();
})
test ( "ExcludeCancel" , () => {
render (< ExcludeCancel />); // 스토리 렌더링
expect ( screen .getByRole ( "button" , { name : "OK" })) .toBeInTheDocument ();
expect ( screenqueryByRole ( "button" , { name : "CANCEL" })). not .toBeInTheDocument ()
})
})
@storybook/test-runner와의 차이점
테스트와 스토리 등록을 한번에 처리해서 중복을 줄이는 방법은 테스트 러너 활용법(스토리의 play function에 단언문 작성하기)과 유사하다.
어떤 방법을 적용할지는 테스트 목적과 각 방법의 장단점을 비교해 결정하자.
Jest에서 스토리를 재사용할 때의 장점
목 모듈 혹은 스파이가 필요한 테스트를 작성할 수 있다.(제스트의 목함수 사용)
실행 속도가 빠르다. (헤드리스 브라우저를 사용하지 않기 때문)
테스트 러너의 장점
테스트 파일을 따로 만들지 않아도 된다.(적은 작업량)
실제 환경과 유사성이 높다. (브라우저를 사용해 CSS가 적용된 상황 재현 가능)
Last updated 5 months ago