<aside> ℹ️

Storybook Test has launched and the full documentation is available here: https://storybook.js.org/docs/writing-tests

</aside>

Snapshot testing is simply rendering a component in a given state, taking a snapshot of the rendered DOM or HTML, and then comparing it against the previous snapshot. They’re convenient to create, but can be difficult and noisy to maintain if the snapshot contains too much information. For UI components, visual tests (easier to review) or component tests (focused on functionality) are usually the better fit. However, there are some cases where snapshot testing may be necessary, such as ensuring an error is thrown correctly.

You can reuse your stories as the basis of snapshot tests within another test environment, like Jest or Vitest. To enable this, Storybook provides the Portable Stories API, which composes your stories with their annotations (args, decorators, parameters, etc) and produces a renderable element for your tests. Portable Stories are available for:

Get started with Portable Stories

If you’re using Storybook Test, your project is already configured to use Portable Stories in Vitest.

If you’re not using Storybook Test or would like to test in another testing environment, please follow the relevant documentation:

Snapshot testing a portable story

Snapshot testing a reusable story is a straightforward process of using composeStories from the Portable Stories API to get a renderable element, rendering that element, and then taking and comparing a snapshot.

This example renders a Button component in Vitest (by reusing one of Button’s stories) and asserts that the rendered HTML snapshot matches.

// Button.test.ts
import { expect, test } from 'vitest';

import { composeStories } from '@storybook/react';

import * as stories from '../stories/Button.stories';

const { Primary } = composeStories(stories);

test('Button snapshot', async () => {
  await Primary.run();
  // You can also use `toMatchSnapshot` to save it to another file
  expect(document.body.firstChild).toMatchInlineSnapshot();
});

Once the test has run, a snapshot will be inserted or created. Then, when you run tests again and the snapshot doesn’t match, the test will fail and you will see output something like this:

FAIL  src/components/ui/Button.test.ts > Button snapshot
Error: Snapshot `Button snapshot 1` mismatched

- Expected
+ Received

  <div>
    <button
-     class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 h-9 px-4 py-2 has-[>svg]:px-3"
+     class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 h-9 px-3 py-2 has-[>svg]:px-3"
      data-slot="button"
    >
      Button
    </button>
  </div>

<aside> 💡

How long did it take you to find what changed? (px-4px-3)

This is exactly why visual tests are so much better for testing the appearance of UI components. Not only is it immediately apparent what has changed, but it also tests the actual appearance your users will see, not merely the CSS applied.

</aside>

Verifying an error is thrown