Bun: Testing Storybook ShadcnUI with Vite React
Testing Storybook with Bun and React
Continuing blog, for Bun from the last post; Bun.sh with React, Typescript, TailwindCSS and Storybook
In this blog, we will explore the basics of Storybook testing with its runner.
Getting Started
We will be following this installation documentation from Storybook Test Runner
Start by installing the add-on
bun add -d @storybook/test-runner
and add new scripts to the package.json
"scripts": {
"test-storybook": "test-storybook"
}
Since the underlying of this package is using Jest and Playwright - we need to run our code first before running the test.
Run the storybook first in the first terminal
bun storybook
Run the test on the second terminal
bun test-storybook
Running this command directly will be a smoke test for every story in the storybook. This is useful to check if there are any errors in mounting or rendering the components.
Adding Form for Interaction
In this example, we will add a Form component using the ShadcnUI Form and Input component.
bunx shadcn-ui@latest add form input
For illustration purposes, we will just use the example from the ShadcnUI Profile Form component. Add this ProfileForm component to the components
folder.
// components/ProfileForm.tsx
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { useForm } from 'react-hook-form'
const formSchema = z.object({
username: z.string().min(2, {
message: 'Username must be at least 2 characters.',
}),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
},
})
const onSubmit = form.handleSubmit((values) => {
// Do something with the form values.
// โ
This will be type-safe and validated.
console.log(values)
})
return (
<Form {...form}>
<form onSubmit={onSubmit} className='space-y-8'>
<FormField
control={form.control}
name='username'
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder='shadcn' {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type='submit'>Submit</Button>
</form>
{form.formState.isSubmitted && (
<div className='p-4 bg-green-100 rounded-md'>
<p className='text-green-800'>
Your profile has been updated successfully.
</p>
</div>
)}
</Form>
)
}
After we have created the ProfileForm
component, we can create a new story in the Storybook.
// stories/ProfileForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react'
import { ProfileForm } from '@/components/ProfileForm'
const meta = {
title: 'Example/ProfileForm',
component: ProfileForm,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof ProfileForm>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {},
}
We can check this in the Storybook it will show like below.
Interactive Testing in Storybook
For the interaction test, we will follow this guideline from Storybook documentation.
Install the library needed for this test.
bun add -d @storybook/testing-library @storybook/jest @storybook/addon-interactions
Now comes the interesting part, writing your user interaction test code. Create a new story for the Filled form.
export const Filled: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
// ๐ Simulate interactions with the component
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com')
await userEvent.type(canvas.getByTestId('password'), 'a-random-password')
// See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
await userEvent.click(canvas.getByRole('button'))
// ๐ Assert DOM structure
await expect(
canvas.getByText(
'Everything is perfect. Your account is ready and we should probably get you started!'
)
).toBeInTheDocument()
},
}
add the import lines as well
import { userEvent, within } from '@storybook/testing-library'
import { expect } from '@storybook/jest'
I faced a type error while writing the test.
hence add this to the compilerOptions
in the tsconfig.json
"types": ["@testing-library/jest-dom"]
Running again the storybook and checking the Filled story of ProfileForm, there will be a Pass test in the interaction tab.
With this, we can even move back to the previous state and play the interaction on our own in the Storybook UI.
We can test again the test-storybook command and see the results of this new interaction test in the test runner.
bun test-storybook --verbose
Running in verbose mode --verbose
, so we can see that the Filled story is different than the rest of the stories here which is "play-test" instead of "smoke-test".
Final Thoughts
Testing is a huge subject to cover - we only cover a small portion of it.
After going through this post, testing with Bun and Storybook in Vite React App seems working fine.
Following this documentation, we can try to run the Storybook in bun runtime as well like so
bun --bun storybook
However, it is still a tracked issue currently in the Bun Repository - therefore we shall await more stability coming into the Bun runtime in the coming weeks.