How to Test React Components in Isolation with Storybook

updated on 30 November 2022

One of the challenges in frontend development is isolating and testing components, especially those made with frameworks like React, Vue, Svelte and others. It is often required to build out other related parts of the application just to test a single component. This gets harder when trying to simulate unique edge cases which requires orchestrating particular configurations with other components. On top of this, tracking and documenting the various state and props of components’ gets tedious to maintain. 

This is where Storybook comes in, which works with many frontend frameworks. It’s an open source tool which makes it easy to test and document components. Let’s dive in. 

Setting Up Story Book with React

The final code for this tutorial is visible on this repository for reference.

fKFRV0yi-phkqz

Prerequisites:

  • NodeJS Installed
  • IDE of your choice

We are going to use React for this tutorial. Create a new React project with the following command.

npx create-react-app sb-testrun

Now that we have our React application, change directory in your terminal to your new application and run the storybook installer. Storybook will automatically determine which framework you’re using. 

npx storybook init

You’ll notice a new .storybook directory that contains some configuration files. We don’t need to touch these at the moment.

Storybook directory containing some configuration files-jgg36

Notice that your package.json has a couple of new scripts that weren’t there before.

    "storybook": "start-storybook -p 6006 -s public",
    "build-storybook": "build-storybook -s public"

We can run Storybook and see the storybook interface by running npm run storybook. This will open a tab with your Storybook. 

Story book_-vnxml

There isn’t much other than some links and example“stories”, but soon enough we’ll build out our Storybook. 

Building a Basic Counter Component

Let’s build a basic component which we can then test using Storybook.

  • create a folder in src called components
  • in that folder create a Counter.js file
  • in that file add the following
import { useState } from "react";
function Counter(props) {
  const [count, setCount] = useState(0);
  const addOne = () => setCount((previous) => previous + 1);
  return <button onClick={addOne}>{count}</button>;
}
export default Counter;

This will provide us a basic button that displays the number of times it has been clicked. To see this on our storybook dashboard we will need to create a story.

Stories: The Building Blocks of Storybook

A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the“interesting” states a component can support.

The storybook dashboard will show any “stories” we create. These are created with files called <name>.stories.js which storybook will detect then load up on the Storybook dashboard. Let’s create a Counter.stories.js file in the src/stories folder with the following contents:

import React from 'react';
import Counter from '../components/Counter';
// This exported obeject determines what is show on the dashboard
export default {
  title: 'Counter',
  component: Counter,
};

export const Basic = () => <Counter/>;

If we open up Storybook with npm run storybook you should see Counter now added to our list of stories and under it the “Basic” story. You can now see and interact with the component in isolation but so far not much else.

Stories - the building blocks of storybook-q8d7z

Let’s modify the component so we can set the starting value of the counter. Update the Counter.js to look like so:

import { useState } from "react";
function Counter({start = 0}) {
  const [count, setCount] = useState(start);
  const addOne = () => setCount((previous) => previous + 1);
  return <button onClick={addOne}>{count}</button>;
}
export default Counter;

Now we can add stories showing the counter with different starting values, let’s update Counter.stories.js like so:

import React from 'react';
import Counter from './components/Counter';
// This exported obeject determines what is show on the dashboard
export default {
  title: 'Counter',
  component: Counter,
};

export const Basic = () => <Counter/>;
export const Five = () => <Counter start={5}/>;
export const Ten = () => <Counter start={10}/>;

Now imagine we had a much more complex component with many props and many different possible permutations. Using Storybook we can create stories for many different possibilities without having to edit the actual application each time we want to test a particular scenario. 

Adding Controls to Your Story

Let’s edit our story so that we can set the props directly from storybook. Luckily, this can be done quite easily by create a template and using it our stories. Update your Counter.stories.js like so:

import React from 'react';
import Counter from './components/Counter';
// This exported obeject determines what is show on the dashboard
export default {
  title: 'Counter',
  component: Counter,
};
// create a template that maps arguments to the component
const Template = (args) => <Counter {...args}/>;
// Define a story that uses the template
export const Basic = Template.bind({});
// Define the starting Arguments for that story
Basic.args = {
    start: 0
}
export const Five = () => <Counter start={5}/>;
export const Ten = () => <Counter start={10}/>;
  • Template defines how the argument object relates to the Counter component.
  • Basic is a story that uses Template to define it can take arguments.
  • Basic.args defines the initial set of arguments, the initial values also help determine the type of inputs that will display in Storybook.

Now when we look at the Storybook dashboard we can set the start prop from Storybook, then hit the little refresh button next to “docs” to have it rerender with the new arguments.

Storybook - controls-uugc8

Writing Documentation in Storybook

We can also use our Storybook dashboard as documentation. We can use MDX to write our documentation. In the src/stories folder create a Counter.mdx file. This will act as documentation for Counter component. The contents of this file are below.

## Counter Component
This component render a button that counts.
### Props
- `start` a number that determines the starting count

We can display this on the dashboard for documentation by updating our Counter.stories.js like so, adding a ‘docs’ parameter:

import React from 'react';
import Counter from '../components/Counter';
import CounterDocs from './Counter.mdx';
// This exported obeject determines what is show on the dashboard
export default {
  title: 'Counter',
  component: Counter,
  // Here is where we set the docs which we imported above
  parameters: {
    docs: {
      page: CounterDocs
    }
  }
};
// create a template that maps arguments to the component
const Template = (args) => <Counter {...args}/>;
// Define a story that uses the template
export const Basic = Template.bind({});
// Define the starting Arguments for that story
Basic.args = {
    start: 0
}
export const Five = () => <Counter start={5}/>;
export const Ten = () => <Counter start={10}/>;
  • We import the .mdx file into a variable CounterDocs
  • We then pass the variable to parameters.docs.page to set it as the docs page

Now if you click on “Docs” on the Storybook dashboard when browsing the Counter story you’ll see our documentation.

Storybook - docs-jaaow

Running Tests on Your Components

We can do a spot test where we manually inspect the component in the browser and change the props using the dashboard to see their effect. However, we can easily introduce automated testing with one of Storybook’s many integrations with testing libraries. Let’s use Jest.

To setup testing, run the following command to install the necessary libraries:

npm install @storybook/test-runner jest@27 --save-dev

Add the following to your scripts in package.json:

"test-storybook": "test-storybook"

Run npm run test-storybrook and it will run all your stories to make sure they all run successfully and print out the results. So now all your stories can double as unit tests! The test-runner can go a lot deeper into snapshot testing and other functionality. 

Other Types of Testing

  • Visual Testing: Using the Chromatic service, you can create UI snapshots between commits to catch visual changes you may not have intended. Visual testing compares the rendered output which is different than snapshot testing which compares the html to be rendered, both can be very useful for catching unexpected UI changes. 
  • Accessibility Testing: Accessibility is extremely important to make sure your application has the largest audience possible and is inclusive. To that end, Storybook has a11y add-on so you can run accessibility testing. These tests will check for accessibility concerns such as how friendly your application is to screen readers.
  • Interaction Testing: With Storybooks interaction testing add-on you can run tests that simulate user interactions with your components to test whether they respond correctly to click events or other interaction events.

Conclusion

Storybook provides a suite a tools to allow frontend developers to sleep better at night by enabling them to experience their components in isolation, create documentation and run a variety of unit tests, snapshot tests, accessibility tests and more.

Meticulous

Meticulous is a tool for software engineers to catch visual regressions in web applications without writing or maintaining UI tests.

Inject the Meticulous snippet onto production or staging and dev environments. This snippet records user sessions by collecting clickstream and network data. When you post a pull request, Meticulous selects a subset of recorded sessions which are relevant and simulates these against the frontend of your application. Meticulous takes screenshots at key points and detects any visual differences. It posts those diffs in a comment for you to inspect in a few seconds. Meticulous automatically updates the baseline images after you merge your PR. This eliminates the setup and maintenance burden of UI testing.

Meticulous isolates the frontend code by mocking out all network calls, using the previously recorded network responses. This means Meticulous never causes side effects and you don’t need a staging environment.

Learn more here.

Authored by Alex Merced

Read more