๐ ๋ค์ด๊ฐ๋ฉฐ
์คํ ๋ฆฌ๋ถ์ component driven development ํจ๋ฌ๋ค์์ ์ํ ์ ์ฉํ ๊ฐ๋ฐ ๋๊ตฌ๋ก
๋จ์ํ UI ๋ฅผ ํ์ธํ๋ ์ฉ๋๋ฅผ ๋์ด์์ ํ ์คํธ๋ฅผ ์ํ ๋ค์ํ ์๋ฃจ์ ๋ ํจ๊ป ์ ๊ณตํด์ฃผ๊ณ ์์ต๋๋ค.
์ด๋ฒ ํฌ์คํธ์์๋ ์ด๋ฅผ ํ์ฉํ ํ ์คํธ ์ ์ฉ ์ฌ๋ก์ ๋ํด ์ดํด๋ณด๊ณ ์ํฉ๋๋ค.
์ฐ์ ์ด๋ฒ ํฌ์คํธ๋ฅผ ์ํ ํ๋ก์ ํธ์ ํจํค์ง ๋ฒ์ ์ ๋ณด๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๐ฆ project dependency
- react/react-dom: 17.0.2
- next: 12.1.1
- storybook: 7.0.4
- typescript: 5.0.3
โจ Next.js + jest & storybook ์ค์ ํ๊ธฐ
๐ Next.js + jest ์ค์
์ ๋ํ ์คํธ, ์ค๋ ์ทํ ์คํธ ์์ฑํ๊ธฐ ์ ์ jest ๋จผ์ ์ค์นํฉ๋๋ค.
Next.js v12
๋ฅผ TypeScript
์ ํจ๊ป ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ด์ ๋ง๋ ์ค์ ์ ์งํํฉ๋๋ค.
Next.js v12
๋ถํฐ๋ Babel ๋์ swc
๋ฅผ ํ์ฉํ ์ค์ ์ ํ๋ฉด ๋ฉ๋๋ค.
ํ์ํ ํจํค์ง ์ค์น
npm i -D jest @types/jest
npm i -D jest-environment-jsdom
npm i -D @testing-library/jest-dom
npm i -D @testing-library/react // โ
NOTE: ํ๋ก์ ํธ์์ ์ฌ์ฉํ๊ณ ์๋ react ๋ฒ์ ์ ํธํ๋๋ ๋ฒ์ ์ผ๋ก ์ค์น
- @testing-library/jest-dom
- DOM ํ ์คํธ๋ฅผ ์ํ ์ ์ฉํ custom matcher ๋ค์ ์ ๊ณต
- @testing-library/react
- React DOM ๋ฐ hook ํ ์คํธ๋ฅผ ์ํ ์ ์ฉํ ์ ํธ๋ฆฌํฐ ํจ์๋ค ์ ๊ณต (render, act, renderHook)
Jest ์ค์ ์ถ๊ฐ
// jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
const nextJest = require('next/jest.js')
const createJestConfig = nextJest({
dir: './',
})
/** @type {import('jest').Config} */
const config = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
}
module.exports = createJestConfig(config)
Jest
์คํ์์ ๊ฐ์ฅ ๋จผ์ ์คํ๋ task
๋ฅผ ๋ฑ๋กํ ์ค์ ํ์ผ์ ์ถ๊ฐํฉ๋๋ค.
์ฌ๊ธฐ์ jest-dom
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
// jest.setup.js
import '@testing-library/jest-dom'
// tsconfig.json
{
"compilerOptions": {
"types": ["jest"], // โ
}
}
๐ Next.js + Storybook 7 ์ค์
๋จผ์ ํ๋ก์ ํธ์ ์คํ ๋ฆฌ๋ถ ์ค์ ์ ํด์ค๋๋ค.
storybook cli
๋ฅผ ์ด์ฉํ๋ฉด ํ์ฌ ํ๋ก์ ํธ์ ๊ฐ์ฅ ์ ํฉํ ์ค์ ์ ์๋์ผ๋ก ํด์ค๋๋ค.
> npx storybook@latest init // ํ์ฌ ์ต์ ๋ฒ์ ๊ธฐ์ค์ผ๋ก storybook@7.0.4๊ฐ ์ค์น๋ฉ๋๋ค.ใฑ
๊ทธ๋ฆฌ๊ณ CSF3 ํฌ๋งท์ผ๋ก ์คํ ๋ฆฌ ํ์ผ์ ์์ฑํฉ๋๋ค.
import type { Meta, StoryObj } from '@storybook/react'
import MOCK_TRANSACTION from '@/__mock__/ready.transaction.json'
import OrderItem from './order-items'
const meta: Meta<typeof OrderItem> = {
title: 'Element / Order Item',
component: OrderItem,
}
export default meta
export const Basic: StoryObj<typeof OrderItem> = {
render: (args) => <OrderItem {...args} />,
args: {
items: MOCK_TRANSACTION.cart.items,
},
}
๋ง์ฝ Context Provider ๊ฐ ํ์ํ๋ค๋ฉด
์ผ๋ถ ์ปดํฌ๋ํธ๋ค์์ provider injection ์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
์ ์ญ์ ์ผ๋ก ์ค์ ์ด ํ์ํ ๊ฒ๋ค์ preview.tsx
์ ์คํ ๋ฆฌ๋ณ๋ก ์ถ๊ฐ๊ฐ ํ์ํ ๊ฒ์ ๊ฐ๋ณ ์คํ ๋ฆฌํ์ผ์ decorator ๋ก ์ถ๊ฐํด์ค๋๋ค.
โจ storybook ๋ฅผ ํ์ฉํ ์ปดํฌ๋ํธ ํ ์คํธ
์ค์ ์ ๋ง์ณค์ผ๋ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ด ๋๋ค.
๐ ์ ๋ ํ ์คํธ
testing-library
์ ํจ๊ป @storybook/react
์์ ์ ๊ณตํ๊ณ ์๋ API๋ค์ ํจ๊ป ํ์ฉํ๋ฉด
์ด๋ฏธ ์์ฑํด๋์ story ํ์ผ์ ์ฌ์ฌ์ฉํ ํ ์คํธ ์ฝ๋ ์์ฑ์ด ๊ฐ๋ฅํฉ๋๋ค.
๐ก โ ๏ธ ์ฃผ์!
Storybook 7 ๋ถํฐ๋ @storybook/react ํจํค์ง์ @storybook/testing-react(https://storybook.js.org/addons/@storybook/testing-react) ๊ฐ ๋ด์ฅ๋์ด์ ์ด์ ๋ ๋ณ๋ ์ค์น๊ฐ ํ์์์ต๋๋ค.
๋ค์์ ์คํ ๋ฆฌํ์ผ์ ์ด์ฉํด์ ์์ฑํ Jest
ํ
์คํธ์ฝ๋ ์์์
๋๋ค.
์คํ ๋ฆฌ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ mock ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ์ฌํ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
import { render } from '@testing-library/react'
import { composeStories } from '@storybook/react'
import * as stories from './radio-box.stories'
const { Basic } = composeStories(stories)
describe('<RadioBox />', () => {
it('should render 4 payment option', () => {
const { getAllByTestId } = render(<Basic />)
const options = getAllByTestId('radio-option')
expect(options).toHaveLength(4)
})
it('should highlight selected option', () => {
const { getAllByTestId } = render(<Basic />)
const creditOption = getAllByTestId('radio-option')[0]
expect(creditOption).toHaveStyle('border: solid 1.2px #368fff;')
})
})
๐ ์ค๋ ์ท ํ ์คํธ
์๊ฐ ํ
์คํธ(visual test
) ํน์ ์๊ฐ์ ํ๊ท ํ
์คํธ(visual regression test
) ๋ ์ฝ๋ ์์ ์ ๋ฐ๋ฅธ UI ๋ฒ๊ทธ๋ฅผ ๊ด๋ฆฌํ๋ ์ข์ ํ
์คํธ ๋ฐฉ๋ฒ์
๋๋ค.
์ด ํ ์คํธ ๋ฐฉ์์์๋ ์คํ ๋ฆฌ ํ์ผ์ ์คํฌ๋ฆฐ์ท์ ๋น๊ตํ์ฌ UI ์ ๋ณ๊ฒฝ์ฌํญ์ ์ถ์ ํฉ๋๋ค.
์คํ ๋ฆฌ๋ถ์ ํด๋ผ์ฐ๋ ํธ์คํ
๋ฐฉ์์ chromatic
๊ณผ self-managed
๋ฐฉ์์ StoryShots
๋ฅผ ํ์ฉํ๋ ๋ ๊ฐ์ง ์ต์
์ ์ ๊ณตํ๋๋ฐ,
์ ๋ ์ฌ์ด๋ ํ๋ก์ ํธ์์ ci workflow ์์ chromatic ์๋ํ ํ ์คํธ ๋จ๊ณ๋ฅผ ์ถ๊ฐํด์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
๊ฐ์ธ์ ์ธ ์๊ฒฌ์ผ๋ก chromatic
์ด ์ค๋
์ท ๋น๊ต ๋ฐ ๋น๋ ํ์คํ ๋ฆฌ๋ฅผ ๊ด๋ฆฌํ๋๋ฐ ์ฉ์ดํ๋ค๊ณ ๋๊ผ์ต๋๋ค. ๐
๐ interaction ํ ์คํธ
์คํ ๋ฆฌ๋ถ 6.4 ๋ถํฐ ๊ณต์์ ์ผ๋ก interaction stories
๋ผ๋ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์๊ฒ ๋์์ต๋๋ค.
interaction test vs jest dom testing
์ด์ฏค์์ interaction test ๊ฐ Jest DOM ํ
์คํธ๋ฅผ ๋์ฒดํ๋ ๊ฒ์ผ๊น?
ํ๋ ์๋ฌธ์ด ๋๋๋ฐ์.
์ด์ ๊ด๋ จํด์ ๋ฉ์ธํ ์ด๋์ ์๊ฒฌ์ด ์์ต๋๋ค.
Use the test runner in most cases
Use Jest/JSDOM (or the tool of your choice)
* If you prefer another testing mechanism
* If there are certain kinds of setup/teardown that you can't achieve with the test runner
์ ์ ์ธํฐ๋ ์
์ด ํฌํจ๋์ด ํ
์คํธ๋ฅผ ์์ฑํ๊ธฐ ๊น๋ค๋กญ๊ฑฐ๋, Jest DOM
์ ๊ตฌํ ํ๊ณ์ ์ผ๋ก ์ธํด
์ค์ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์ ์ปดํฌ๋ํธ ๋จ์๋ก ํ
์คํธ๋ฅผ ์ํํด์ผํ๋ ๊ฒฝ์ฐ์๋ interaction test
๋ฅผ ํ์ฉํ๊ณ
๊ทธ ์ธ์๋ ์ ํธ๋ฐฉ์์ ๋ฐ๋ผ Jest DOM
์ ํ์ฉํ์ฌ ํ
์คํธ๋ฅผ ์ํํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
interaction test
๋ฅผ ์ง์ ์ฌ์ฉํด๋ณด๋, ์ค์ ๋ธ๋ผ์ฐ์ ์์ ์๊ฐ์ ์ธ ๋๋ฒ๊น
์ด ๊ฐ๋ฅํ ๊ฒ์ด ์ข ๋ ํธ๋ฆฌํ ๊ฒ ๊ฐ์ต๋๋ค.
๋ค๋ง interaction test
๊ฐ ์ค์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํ์ฉํ์ฌ ํ
์คํธ๋ฅผ ์คํํ๊ธฐ ๋๋ฌธ์
ํ ์คํธ ์คํ์ ํ์ํ ์๊ฐ์ด ์กฐ๊ธ ๋ ๊ฑธ๋ฆฐ๋ค๋ ๊ฒ๋ ์ ์ํ ํ์๊ฐ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
๋ชจ๋ ์ ๋ ํ ์คํธ ๋ก์ง์ ์ ๋ถ ๋์ฒดํ๊ธฐ๋ณด๋ค๋ ๋ ํ ์คํธ ๋ฐฉ์์ ์ง์๋ฒ์์ ๊ทผ๊ฑฐํด
์ํฉ์ ๋ง๊ฒ ๋ ํ ์คํธ ๋๊ตฌ๋ฅผ ์ ์ ํ ํ์ฉํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค์ ๐
ํจํค์ง ์ค์น
npm i -D @storybook/testing-library
npm i -D @storybook/jest
npm i -D @storybook/addon-interactions
@storybook/testing-library
- interaction addon ๊ณผ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ํ testing-library instrument ๋ฒ์
@storybook/jest
- storybook interaction ์ ์ํ jest integration ํจํค์ง
main.ts ์ค์ ์ถ๊ฐํ๊ธฐ
// .storybook/main.ts
const config: StorybookConfig = {
// ...
addons: [
// .. Other Storybook addons
'@storybook/addon-interactions', // ๐ Register the addon
],
};
๊ทธ๋ผ ์์์ ์์ฑํ unit test ๋ฅผ interaction test ๋ก ๋ฐ๊ฟ๋ด ์๋ค.
import { within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export const Basic: StoryObj<typeof RadioBox> = {
// ...
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement)
await step('should render 4 payment option', () => {
const options = canvas.getAllByTestId('radio-option')
expect(options).toHaveLength(4)
})
},
}
play function ์ ๋ ๋๋ง๋ ์ปดํฌ๋ํธ์ ๋์(interaction) ์ ํ ์คํธํด๋ณผ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
userEvent
๋ก event trigger ๋ ๊ฐ๋ฅํฉ๋๋ค.
DemoStory.play = async ({ canvasElement }) => {
const canvas = within(canvasElement)
const inputElement = canvas.getByRole<HTMLInputElement>('textbox')
const deleteButton = canvas.getByRole('button')
await userEvent.type(inputElement, 'test', { delay: 100 })
expect(inputElement.value).toBe('test')
await userEvent.click(deleteButton)
}
์ด๋ฅผ ํ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ธํฐ๋ ์ ํ ์คํธ๋ ๊ฐ๋ฅํฉ๋๋ค.
๐ Storybook test runner
Storybook test runner
๋ ์ฐ๋ฆฌ๊ฐ ์์ฑํ ์คํ ๋ฆฌ๋ค์ ์คํ ๊ฐ๋ฅํ ํ
์คํธ๋ก ๋ณํํด์ค๋๋ค.
์คํ ๋ฆฌ๊ฐ ์ ๋ ธ์ถ๋๋์ง, ๊ทธ๋ฆฌ๊ณ ์คํ ๋ฆฌ์ interaction test ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ํ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค.
test runner
ํจํค์ง๋ฅผ ์ค์นํด์ค๋๋ค.
> npm i -D @storybook/test-runner
๊ทธ๋ฆฌ๊ณ ํ ์คํธ ์คํ์ ์ํ script ๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
// package.json
{
"scripts": {
"storybook:test": "test-storybook"
}
}
test runner
๋ ํญ์ ์คํ ๋ฆฌ๋ถ์ด ์คํ์ค์ธ ์ํ์์ ์คํ๋์ด์ผํฉ๋๋ค.
๋ฐ๋ผ์ ๋จผ์ ์คํ ๋ฆฌ๋ถ์ ์คํ์ํจ ๋ค์, ์ task ๋ฅผ ์คํํด์ฃผ์ธ์!
๋ฐฐํฌ๋์ง ์์ ์คํ ๋ฆฌ๋ถ์ CI ํ๊ฒฝ์์ test runner ์ ํจ๊ป ์คํํ๊ธฐ
๊ธฐ๋ณธ์ ์ผ๋ก Storybook test runner
๋ storybook ์๋ฒ๊ฐ ์คํ๋ ์ํ์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
๋๋ฌธ์ CI ํ๊ฒฝ์์๋ ๋ณ๋์ ํ๋ก์ธ์ค์์ ์คํ ๋ฆฌ๋ถ์ ๋น๋ํ๊ณ , ์ด๋ฅผ ์ด์ฉํด ์ ์ ํ์ผ์ ์๋นํ๋ ์๋ฒ๋ฅผ ์คํ์ํจ ๋ค์ ํ ์คํธ๋ฅผ ์ํํด์ผํฉ๋๋ค.
์ด ๊ณผ์ ์ ๋์์ค ํจํค์ง(concurrently
)๋ฅผ ์ค์นํฉ๋๋ค.
npm i -D concurrently
CI ๋ฅผ ์ํ run script ๋ฅผ ์ถ๊ฐํ ๋ค์ ๋ก์ปฌ์์ ์คํํด๋ด ๋๋ค.
// package.json
scripts: {
"storybook:test": "test-storybook",
"storybook:build": "storybook build",
"storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"npm run storybook:build --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && npm run storybook:test --maxWorkers=2\"",
}
> npm run storybook:ci
์ด์ ๊ฐ์ ์ฌ์ฉํ๋ CI ํ๊ฒฝ์ ๋ง๊ฒ ํ์ดํ๋ผ์ธ์ ์ถ๊ฐํด์ค๋๋ค.
์ ๋ Github Action ์ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ์ถ๊ฐํด์คฌ์ต๋๋ค.
steps:
- name: storybook test
run: npm run storybook:ci
๐ storybook msw addon
์คํ ๋ฆฌ์์ ํ ์คํธ๊ฐ ํ์ํ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ถ API ์ ๋ฐ์ดํฐ์ ์์กดํ๊ณ ์๋ค๋ฉด ์ด๋ป๊ฒ ํ ๊น์?
๋ชจํน๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋๋ก fetch
๋ฅผ ๊ฐ์ธ๋ ๋ฐฉ๋ฒ๋ ์๊ฒ ์ง๋ง, ๋ชจ๋ fetch
๋ง๋ค ์ด ๊ณผ์ ์ด ํ์ํฉ๋๋ค.
๋์ ์ msw ๋ฅผ ์ด์ฉํด์ API ๋ชจํน์ด ํ์ํ endpoint ๋ฅผ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉํฅ์ด ์ ์ง๊ด๋ฆฌ์ ์ข ๋ ์ฉ์ดํฉ๋๋ค.
1๏ธโฃ msw ์ storybook msw addon ์ค์น
๋จผ์ ํ์ํ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค.
> npm i msw msw-storybook-addon -D
msw
๋ ์ด๊ธฐ ์ค์ ์ ์ํ script ๋ฅผ ์ ๊ณตํด์ฃผ๊ณ ์์ต๋๋ค.
public/
ํ์์ msw ์ฌ์ฉ์ ์ํ ์ด๊ธฐ ์ค์ ์ ์งํํด์ค๋๋ค.
> npx msw init public/
2๏ธโฃ addon ์ค์
์ด์ ์คํ ๋ฆฌ๋ถ์์ msw
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ์ค์ ์ ์ถ๊ฐํด์ค๋๋ค.
// ./storybook/preview.(ts|tsx)
import type { Preview } from '@storybook/react'
import { initialize, mswDecorator } from 'msw-storybook-addon';
initialize() // โ
Initialize MSW
const preview: Preview = {
decorators: [mswDecorator, ...] // โ
serve msw decorator globally
// ... and other config options
}
export default preview
// ./storybook/main.ts
const config: StorybookConfig = {
staticDirs: ['../public'], // ๐ Configures the static asset folder in Storybook
// ... and other config options
};
3๏ธโฃ global msw handler ์์ฑํ๊ธฐ
๋ชจ๋ ์คํ ๋ฆฌ์์ ํธ์ถ์ด ํ์ํ API ์ ๊ฒฝ์ฐ global handler ๋ก ๋ฑ๋กํด๋๋ฉด ํธ๋ฆฌํฉ๋๋ค.
๋ง์ฝ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ /api/get/some/data
๋ผ๋ API ํธ๋ค๋ฌ๊ฐ ์๋ค๋ฉด,
// src/mocks/handlers.js
import { rest } from 'msw'
export const handlers = {
GET_SOME_DATA: rest.get(
'/api/get/some/data',
(req, res, ctx) => {
return res(ctx.status(200), ctx.json({ data: 'some' }))
},
),
}
.storybook/preview.js
์์ ํด๋น ํธ๋ค๋ฌ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด global decorator
๋ก ์ถ๊ฐํ๋ฉด๋ฉ๋๋ค.
import { handlers } from '../src/mocks/handlers'
const preview: Preview = {
parameters: {
msw: {
handlers: {
plcc: [handlers.GET_SOME_DATA]
}
}
}
// ... and other config options
}
export default preview
4๏ธโฃ ๊ฐ๋ณ story ๋ง๋ค msw handler ์ถ๊ฐํ๊ธฐ
๊ฐ๋ณ ์คํ ๋ฆฌ์์ handler ๋ฅผ ์ ์ํ ์๋ ์์ต๋๋ค.
์คํ ๋ฆฌ๋ถ์ global handler ์ story handler ๋ฅผ ๋ณํฉํด์ API ์์ฒญ์ ์ฒ๋ฆฌํฉ๋๋ค.
๋ค์์ ๊ฐ๋ณ ์คํ ๋ฆฌ์์ benefitPromotion
์ด๋ผ๋ ๋ชจํน๋ API ๋ฅผ ์ถ๊ฐํ๋ ์์์
๋๋ค.
import { handlers } from '@/__mocks__/handlers'
export const Basic: StoryObj<typeof PaymentGateway> = {
parameters: {
msw: {
handlers: {
benefitPromotion: [handlers.GET_BENEFIT_PROMOTION],
},
},
},
// ... and other config options
}
5๏ธโฃ API whitelist ์ถ๊ฐํ๊ธฐ
๊ฒฝ์ฐ์๋ฐ๋ผ ํน์ API ์์ฒญ์ ๋ฌด์ํด์ผํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์ ์ ๊ฒฝ์ฐ ์ด๋ฏธ์ง ์์ฒญ์ ๋ณ๋์ ๋ฏธ๋ค์จ์ด์์ ์ฒ๋ฆฌํ๊ณ ์์๊ธฐ ๋๋ฌธ์,
์ด๋ฏธ์ง URL ํ์์ผ๋ก ์ค๋ ์์ฒญ์ ๋ฌด์ํด์ฃผ๋๋ก ๋ค์ ์ค์ ์ ์ถ๊ฐํด์คฌ์ต๋๋ค.
// .storybook/preview.(ts|tsx)
// โ
Initialize MSW
initialize({
onUnhandledRequest: ({ url }) => {
if (url.pathname.startsWith('/payment/static/images')) {
return // storybook middleware ์์ ์ฒ๋ฆฌํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ฌด์ํฉ๋๋ค.
}
},
})
๐ ์ฐธ๊ณ ์๋ฃ
Mock Service Worker Addon | Storybook: Frontend workshop for UI development
'๐จโ๐ป web.dev > fe' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Web Component์ ํจ๊ป ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ (0) | 2023.08.25 |
---|---|
Storybook loaders ๋ก story์ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ฃผ์ ํ๊ธฐ (0) | 2023.06.24 |
React Framer Motion ํบ์๋ณด๊ธฐ (0) | 2023.03.27 |
ํ๋ก ํธ์๋ E2E ํ ์คํ (with Cypress) (2) | 2023.03.19 |
Next.js styled-component ์ค์ ํ๊ธฐ (SSR FOUC ์ด์) (0) | 2022.11.27 |
๐ฌ ๋๊ธ