Bun: Testing Storybook ShadcnUI with Vite React

Bun: Testing Storybook ShadcnUI with Vite React

Testing Storybook with Bun and React

Sep 26, 2023ยท

4 min read

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 {
} 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.

  return (
    <Form {...form}>
      <form onSubmit={onSubmit} className='space-y-8'>
          render={({ field }) => (
                <Input placeholder='shadcn' {...field} />
                This is your public display name.
              <FormMessage />
        <Button type='submit'>Submit</Button>

      {form.formState.isSubmitted && (
        <div className='p-4 bg-green-100 rounded-md'>
          <p className='text-green-800'>
            Your profile has been updated successfully.

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(
        'Everything is perfect. Your account is ready and we should probably get you started!'

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.

At the time of writing this post, we are mostly still running with NodeJS to execute the command.

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.