Skip to content

All about React component testing with Storybook

Jaied Al Sabid edited this page Oct 18, 2024 · 5 revisions

All about React component testing with Storybook

Testing is an essential part of any software development process because it ensures that your application behaves as expected and helps prevent bugs. When you're working with React, testing becomes even more important, especially as your application grows in complexity. Let’s break down the key aspects of testing in React and how you can get started.


Why Testing is Important

  1. Prevents Bugs: Testing ensures that your components and logic are working correctly, reducing bugs that could negatively impact users.
  2. Improves Code Quality: Writing tests forces you to think about how your code should behave, leading to better structure and design.
  3. Facilitates Refactoring: With tests in place, you can make changes to your codebase with confidence. If something breaks, the tests will let you know.
  4. Ensures Maintainability: As your application grows, tests help ensure that adding new features or refactoring code doesn’t unintentionally break existing functionality.

Types of Testing in React

  1. Unit Testing:

    • What is it? → Unit tests focus on testing individual functions or components in isolation. For example, you can test a single React component to ensure it renders correctly with given props.
    • Why is it important? → It helps ensure that each small part of your application works as expected on its own.
  2. Integration Testing:

    • What is it? → Integration tests check how different parts of your application work together. For example, you can test a parent component and its child components together.
    • Why is it important? → It ensures that your components or functions are correctly interacting with one another.
  3. End-to-End (E2E) Testing:

    • What is it? → E2E tests simulate how a real user would interact with your application. These tests check the entire flow of your application (e.g., filling out a form and submitting it).
    • Why is it important? → E2E testing ensures that the application behaves correctly from the user's perspective, covering real-world scenarios.
  4. Snapshot Testing:

    • What is it? → Snapshot tests store a "snapshot" of a rendered component, and if the component's output changes unexpectedly, the test will fail.
    • Why is it important? → It’s great for tracking UI changes and ensuring components render consistently over time.

Best Frameworks for React Testing

  1. Jest:

    • What is it? → Jest is a popular testing framework developed by Facebook. It is widely used for React applications due to its simplicity, speed, and powerful features.
    • Features: Comes with built-in support for mock functions, snapshot testing, and code coverage.
    • Why use Jest? → It’s the default choice for testing in React due to its excellent integration with the React ecosystem.
  2. React Testing Library:

    • What is it? → React Testing Library is a lightweight testing library that works with Jest to test your React components.
    • Features: It focuses on testing how users interact with your components rather than testing the internals (e.g., state or implementation details).
    • Why use React Testing Library? → It promotes best practices by encouraging you to test from the user’s perspective.
  3. Cypress (for E2E testing):

    • What is it? → Cypress is a fast and reliable end-to-end testing framework.
    • Features: It allows you to write tests that simulate real user interactions and helps you automate UI testing.
    • Why use Cypress? → It’s excellent for testing entire user flows and ensuring your app works as expected in the browser.
  4. Playwright (for E2E testing)

    • What is it? → Playwright is a modern, open-source automation library that supports multiple browsers (Chromium, Firefox, and WebKit).
    • Features: It offers a robust API for interacting with web pages, cross-browser testing, and headless execution.
    • Why use Playwright? → It provides a unified API for testing across different browsers, making it a versatile choice for E2E testing.

How to Write Tests in React

Let's see how we can complete the setup for testing and write a test case for a component. We will use Storybook and some testing libraries that Storybook suggested.

  1. Install the testing libraries:

    Run the following command to install the required libraries for the testing environment:

npm install --save-dev @storybook/addon-a11y @storybook/addon-interactions @storybook/test-runner @storybook/test playwright axe-playwright
  1. Add test scripts in the package.json.

test is for running all the tests and test:watch is for running tests in watch mode.

{
    "scripts": {
        "test": "test-storybook",
        "test:watch": "test-storybook --watch",
    }
}
  1. Update the storybook config: .storybook/main.js

Include @storybook/addon-a11y and @storybook/addon-interactions to the addons.

const config = {
	stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
	addons: [
		...
		'@storybook/addon-interactions',
		'@storybook/addon-a11y',
	],
	...
};
export default config;
  1. Add Test runner config: .storybook/test-runner.js
const { injectAxe, checkA11y } = require('axe-playwright');
 
/*
 * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api
 * to learn more about the test-runner hooks API.
 */
module.exports = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page) {
    await checkA11y(page, '#storybook-root', {
      detailedReport: true,
      detailedReportOptions: {
        html: true,
      },
    });
  },
};
  1. Write a simple React component:
import React from 'react';

const Button = ({ children }) => {
  return <button>{children}</button>;
};

export default Button;
  1. Write a test for the component with the play function:

Storybook's play functions are small code blocks that execute after a story is rendered. They're particularly useful in combination with addon-interactions, enabling you to create and test component interactions that would otherwise require manual user intervention.

import { expect, userEvent, within } from '@storybook/test';
import Button from './button';

export default {
	title: 'Components/Button',
	component: Button,
	parameters: {
		layout: 'centered',
	},
	tags: [ 'autodocs' ],
	argTypes: {
		children: {
			name: 'children',
			table: {
				type: { summary: 'string' },
				defaultValue: { summary: '' },
			},
		},
	},
};



export const Default = {
	args: {
		children: 'Button',
	},
	play: async ( { canvasElement } ) => {
	    const canvas = within( canvasElement );
	    // Find the button element
	    const button = await canvas.findByRole( 'button' );
	    // Check if the button contains the text 'Button'
	    await expect( button ).toHaveTextContent( 'Button' );
            await userEvent.click( button );
	    // Check if the button is focused
	    await expect( button ).toHaveFocus();
    },
};
  1. What’s happening here?

    • The play function defines the test scenario.
    • canvasElement is passed as an argument, representing the rendered story.
    • canvas is created using within to access elements within the rendered story.
    • findByRole is used to find the button element using its semantic role "button".
    • The test asserts that the button's text content is indeed "Button".
    • userEvent.click simulates a user clicking the button.
    • Finally, the test asserts that the button has focus after being clicked.
  2. Run your tests:

Run the following commands to run the test.

  • npm run storybook
  • npm run test
  1. You can run the test and see what is happening step by step in the browser. Open your story in the browser and check your interactions panel, once the Storybook finishes rendering it will execute the steps defined within the play function, and interact with the component similar to how a user would do it. In the interactions panel, you can see the exact steps your test took. You can also use the controls there to pause, restart, or step through each action one by one.

Best Practices for Writing Tests

  1. Test Behavior, Not Implementation: Focus on testing how your components behave and how they are used, not their internal implementation details. This makes your tests more robust.
  2. Keep Tests Small and Focused: Unit tests should be small and test one thing. Avoid writing large tests that cover too many scenarios at once.
  3. Write Tests for Edge Cases: Think of possible edge cases (like missing data, or incorrect inputs) and write tests for them.
  4. Use Mocks When Needed: When testing components that depend on external data (e.g., API calls), use mocks to simulate the behavior of external services.
  5. Maintain Test Coverage: Test as much of your code as possible, but focus on critical parts like business logic and user interactions.

With these tools and strategies, you’ll be well on your way to writing effective and maintainable tests for your React applications. Testing helps improve the quality of your code and also gives you confidence when making changes down the road!


Learn more about: