userEvent
sample.stories.ts
import { userEvent, within } from '@storybook/test'
export const TestStory: Story = {
tags: ['!autodocs'], // ドキュメントに含めない
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement)
const inputEl: HTMLInputElement = canvas.getByTestId('sample-input') // data-testidがsample-inputの要素
await userEvent.click(inputEl) // click
// key入力
inputEl.focus()
await userEvent.keyboard('{ArrowUp}') // 上矢印ボタン
await userEvent.keyboard('{ArrowDown}') // 下矢印ボタン
await userEvent.keyboard('{Enter}') // Enterキー
await userEvent.keyboard('{Escape}') // Escapeキー
await userEvent.keyboard('{ }') // Spaceキー
// タイピング入力
await userEvent.type(inputEl, 'おはようございます¥nこんにちは') // タイピング
await userEvent.clear(inputEl) // 入力をクリア
}
}
step, expect, fn
sample.stories.ts
import { userEvent, within, expect, fn } from '@storybook/test'
export const TestStory: Story = {
argTypes: {
'onUpdate:modelValue': {
table: { category: 'events', type: { summary: '[v: string]' }, disable: true }, // 自動で表示されるupdateイベントと重複のためdisableとしている
},
},
args: {
modelValue: '',
'onUpdate:modelValue': fn(),
},
play: async ({ args, canvasElement, step }) => {
const canvas = within(canvasElement)
const inputEl: HTMLInputElement = canvas.getByTestId('sample-input') // data-testidがsample-inputの要素
await step('更新イベントが正しく発火するか', async () => {
await userEvent.type(inputEl, '東京都')
await expect(args['onUpdate:modelValue']).lastCalledWith('東京都')
})
}
}
waitFor
.storybook/preview.ts
import { configure } from '@storybook/test'
configure({
asyncUtilTimeout: 6000 // グローバルにwaitForの最大待機時間を6000msに設定
})
sample.stories.ts
import { userEvent, within, expect, waitFor } from '@storybook/test'
export const TestStory: Story = {
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement)
const inputEl: HTMLInputElement = canvas.getByTestId('sample-input') // data-testidがsample-inputの要素
// waitForは反映に時間のかかる処理に使用する
// 関数内のexpectが全て成功するか、configureのasyncUtilTimeoutが経過するまで、50msごとにexpectを繰り返す
// asyncUtilTimeoutが経過しても成功しない場合は失敗となる。
await waitFor(async () => {
await userEvent.type(inputEl, 'おはよう')
await expect(inputEl.value).toBe('こんばんは') // どんなに待ってもvalueが'こんばんは'になることはないのでエラーとなる
await expect(inputEl).toBeVisible() // display: none;の場合はfalse
}
}
}
play関数を再利用する
公式ドキュメント:https://storybook.js.org/docs/writing-stories/play-function#querying-elements
- 再利用するstoryの型にはRequired<Pick<Story, ‘play’>>を追加する必要がある
- 再利用する時に呼び出すstoryに渡す際にはcontextを全て渡す
sample.stories.ts
// 再利用するstoryの型にはRequired<Pick<Story, 'play'>>を追加する必要がある
export const StoryA: Story & Required<Pick<Story, 'play'>> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
....略
}
}
export const StoryB: Story = {
play: async (context) => { // 公式ドキュメントではasync ({context})となっているがエラーとなる
const canvas = within(context.canvasElement)
await StoryA.play(context) // StoryAが実行される
....略 // StoryAを実行した後の処理を記述する
}
}
mockの値を上書きする
jest公式ドキュメント:https://jestjs.io/ja/docs/mock-function-api
beforeEach内で上書きして使用することができる。
テストするコンポーネント内でapiを呼んでいる場合、そのmockApiの返却値を変更してテストする時などに使用する
以下サンプルでは、storiesでmockを定義しているが、実際は別のファイルで定義したmockApiをimportして上書きする使い方をすることが多い
sample.stories.ts
import { fn } from '@storybook/test'
const mock = fn((): string => 'あいうえお').mockName('mockData')
const asyncMock = fn(async (): Promise<string> => 'かきくけこ').mockName('asyncMock')
export const TestStory: Story = {
async beforeEach() {
mock.mockReturnValue('アイウエオ')
asyncMock.mockResolvedValue('カキクケコ')
},
play: async () => {
mock() // 'あいうえお'ではなく、beforeEachで上書きした'アイウエオ'が返却される
await asyncMock() // 'かきくけこ'ではなく、beforeEachで上書きした'カキクケコ'が返却される
}
}