Join live session: Top 8 Storybook myths holding your team back
Docs
Storybook Docs

Unit tests

Teams test a variety of UI characteristics using different tools. Each tool requires you to replicate the same component state over and over. That’s a maintenance headache. Ideally, you’d set up your tests in the same way and reuse that across tools.

Storybook enables you to isolate a component and capture its use cases in a *.stories.js|ts file. Stories are standard JavaScript modules cross-compatible with the whole JavaScript ecosystem.

Stories are a practical starting point for UI testing. Import stories into tools like Jest, Testing Library, Vitest and Playwright, to save time and maintenance work.

Write a test with Testing Library

Testing Library is a suite of helper libraries for browser-based interaction tests. With Component Story Format, your stories are reusable with Testing Library. Each named export (story) is renderable within your testing setup. For example, if you were working on a login component and wanted to test the invalid credentials scenario, here's how you could write your test:

Form.test.ts|tsx
import { fireEvent, render, screen } from '@testing-library/react';
 
import { composeStory } from '@storybook/react';
 
import Meta, { InvalidForm as InvalidFormStory } from './LoginForm.stories'; //👈 Our stories imported here.
 
const FormError = composeStory(InvalidFormStory, Meta);
 
test('Checks if the form is valid', () => {
  render(<FormError />);
 
  const buttonElement = screen.getByRole('button', {
    name: 'Submit',
  });
 
  fireEvent.click(buttonElement);
 
  const isFormValid = screen.getByLabelText('invalid-form');
  expect(isFormValid).toBeInTheDocument();
});

You can use Testing Library out-of-the-box with Storybook Interaction Testing.

Once the test runs, it loads the story and renders it. Testing Library then emulates the user's behavior and checks if the component state has been updated.

Configure

By default, Storybook offers a zero-config setup for React and other frameworks via addons, allowing you to run your stories as tests with Testing Library. However, if you're running tests and you've set up specific configurations in your Storybook instance (e.g., global decorators, parameters) that you want to use in your tests, you'll need to extend your test setup to include these configurations. To do so, create a setup.js|ts file as follows:

setupFile.js|ts
// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';
 
import { setProjectAnnotations } from '@storybook/react';
 
setProjectAnnotations(globalStorybookConfig);

Update your test script to include the configuration file:

{
  "scripts": {
    "test": "react-scripts test --setupFiles ./setupFile.js"
  }
}

Override story properties

By default, the setProjectAnnotations function injects into your existing tests any global configuration you've defined in your Storybook instance (i.e., parameters, decorators in the preview.js|ts file). Nevertheless, this may cause unforeseen side effects for tests that are not intended to use these global configurations. To avoid this, you can override the global configurations by extending either the composeStory or composeStories functions to provide test-specific configurations. For example:

Form.test.ts|tsx
import { composeStories } from '@storybook/react';
 
import * as FormStories from './LoginForm.stories';
 
const { ValidForm } = composeStories(FormStories, {
  decorators: [
    // Define your story-level decorators here
  ],
  globalTypes: {
    // Define your global types here
  },
  parameters: {
    // Define your story-level parameters here
  },
});

Run tests on a single story

You can use the composeStory function from the appropriate framework or supported addon to allow your tests to run on a single story. However, if you're relying on this method, we recommend that you supply the story metadata (i.e., the default export) to the composeStory function. This ensures that your tests can accurately determine the correct information about the story. For example:

Form.test.ts|tsx
import { fireEvent, render, screen } from '@testing-library/react';
 
import { composeStory } from '@storybook/react';
 
import Meta, { ValidForm as ValidFormStory } from './LoginForm.stories';
 
const FormOK = composeStory(ValidFormStory, Meta);
 
test('Validates form', () => {
  render(<FormOK />);
 
  const buttonElement = screen.getByRole('button', {
    name: 'Submit',
  });
 
  fireEvent.click(buttonElement);
 
  const isFormValid = screen.getByLabelText('invalid-form');
  expect(isFormValid).not.toBeInTheDocument();
});

Combine stories into a single test

If you intend to test multiple stories in a single test, use the composeStories function from the appropriate framework or supported addon. The function will process every component story you've specified, including any args or decorators you've defined. For example:

Form.test.ts|tsx
import { fireEvent, render, screen } from '@testing-library/react';
 
import { composeStories } from '@storybook/react';
 
import * as FormStories from './LoginForm.stories';
 
const { InvalidForm, ValidForm } = composeStories(FormStories);
 
test('Tests invalid form state', () => {
  render(<InvalidForm />);
 
  const buttonElement = screen.getByRole('button', {
    name: 'Submit',
  });
 
  fireEvent.click(buttonElement);
 
  const isFormValid = screen.getByLabelText('invalid-form');
  expect(isFormValid).toBeInTheDocument();
});
 
test('Tests filled form', () => {
  render(<ValidForm />);
 
  const buttonElement = screen.getByRole('button', {
    name: 'Submit',
  });
 
  fireEvent.click(buttonElement);
 
  const isFormValid = screen.getByLabelText('invalid-form');
  expect(isFormValid).not.toBeInTheDocument();
});

Troubleshooting

Run tests in other frameworks

Storybook provides community-led addons for other frameworks like Vue 2 and Angular. However, these addons still lack support for the latest stable Storybook release. If you're interested in helping out, we recommend reaching out to the maintainers using the default communication channels (GitHub and Discord server).

The args are not being passed to the test

The components returned by composeStories or composeStory not only can be rendered as React components but also come with the combined properties from the story, meta, and global configuration. This means that if you want to access args or parameters, for instance, you can do so:

Button.test.ts|tsx
import { render, screen } from '@testing-library/react';
 
import { composeStories } from '@storybook/react';
 
import * as stories from './Button.stories';
 
const { Primary } = composeStories(stories);
 
test('reuses args from composed story', () => {
  render(<Primary />);
 
  const buttonElement = screen.getByRole('button');
  // Testing against values coming from the story itself! No need for duplication
  expect(buttonElement.textContent).toEqual(Primary.args.label);
});

Learn about other UI tests