Using MSW in Storybook to Mock Sitecore XM Cloud GraphQL Queries

Simplifying Sitecore GraphQL testing in Storybook with MSW with Next,js

November 22, 2024

By Sohrab Saboori

Mocking Sitecore GraphQL Calls in Storybook for Next.js Sitecore XM Cloud

In Sitecore development, GraphQL plays a crucial role in fetching structured and hierarchical content. However, testing components that rely on live GraphQL endpoints can be cumbersome, especially when backend services are incomplete or unstable.

Fortunately, MSW (Mock Service Worker) and its Storybook addon offer a seamless way to mock GraphQL responses, allowing developers to simulate real-world scenarios directly in Storybook.

In this guide, we’ll show you how to mock Sitecore GraphQL queries in Storybook for a Next.js Sitecore XM Cloud project, ensuring your components are robust and reliable.

Why Mock GraphQL Queries in Storybook?

GraphQL allows you to fetch complex content efficiently, but testing against live data isn’t always practical. Mocking GraphQL queries helps you:

  • Ensure Consistent Results: Develop and test components with stable, predictable data.
  • Speed Up Development: Work without waiting for backend availability or dealing with slow responses.
  • Cover Edge Cases: Simulate various scenarios, such as missing data or error states, without impacting the live environment.

By mocking GraphQL queries, you gain full control over the data your components receive, making the development process smoother and more efficient.

Step 1: Create a GraphQL API Route

Let’s begin by setting up a GraphQL API route in your Next.js project. This route will interact with the Sitecore GraphQL endpoint to fetch content.

Example API Route (pages/api/carouselCards.ts):

import { gql, GraphQLClient } from 'graphql-request';
import config from 'temp/config';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const { sitecorePath, sitecoreLanguage } = req.body;

    const data = await fetchCarouselData(sitecorePath, sitecoreLanguage);

    res.status(200).json(data);
  } catch (error) {
    console.error('Error fetching Carousel Data:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

const fetchCarouselData = async (sitecorePath: string, sitecoreLanguage: string) => {
  const graphQLClient = new GraphQLClient(config.graphQLEndpoint);
  graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey);

  const query = gql`
    query GetCarouselData($path: String!, $language: String!) {
      item(path: $path, language: $language) {
        id
        name
        fields {
          title {
            value
          }
          description {
            value
          }
        }
      }
    }
  `;

  return graphQLClient.request(query, {
    path: sitecorePath,
    language: sitecoreLanguage,
  });
};

Step 2: Create a Custom Hook to Fetch Data

To fetch data from the GraphQL API route, we’ll create a custom hook using SWR. This hook allows you to handle data fetching and caching effortlessly. For more on SWR, see this comprehensive guide.

Custom Hook (hooks/useCarouselData.ts):

import useSWR from 'swr';

type CarouselArgs = {
  sitecorePath: string;
  sitecoreLanguage: string;
};

const fetcher = async (url: string, body: CarouselArgs) => {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
  return response.json();
};

export const useCarouselData = (args: CarouselArgs) => {
  const { data, error, isValidating } = useSWR(['/api/carouselCards', args], ([url, body]) =>
    fetcher(url, body)
  );

  return {
    carouselData: data,
    isLoading: isValidating,
    error,
  };
};

Step 3: Mocking GraphQL Calls in Storybook

Now that we have our API route and custom hook, it’s time to mock the GraphQL query in Storybook using MSW.

Here’s a simplified version of the mock data for the GetCarouselData query:

const mockGraphQLResponse = {
  data: {
    item: {
      id: 'mock-id',
      name: 'Mocked Carousel',
      fields: {
        title: { value: 'Mocked Title' },
        description: { value: 'This is a mocked description.' },
      },
    },
  },
};

Add Mock Handlers

You can mock the GraphQL query at the story level or apply it globally across all stories.

1. Per Story Mocking

Use this method for testing isolated components with specific scenarios.

import { graphql } from 'msw';
import { Meta } from '@storybook/react';

export default {
  title: 'Components/Carousel',
  component: CarouselComponent,
  parameters: {
    msw: {
      handlers: [
        graphql.query('GetCarouselData', (req, res, ctx) => {
          return res(ctx.data(mockGraphQLResponse));
        }),
      ],
    },
  },
} as Meta;

2. Global Mocking in preview.tsx

Apply global handlers to make mocks available across all stories.

import { graphql } from 'msw';

export const parameters = {
  msw: {
    handlers: [
      graphql.query('GetCarouselData', (req, res, ctx) => {
        return res(ctx.data(mockGraphQLResponse));
      }),
    ],
  },
};

Step 4: Using the Custom Hook in a Component

Here’s how you can use the useCarouselData hook in your component:

const CarouselComponent = ({ sitecorePath, sitecoreLanguage }: { sitecorePath: string; sitecoreLanguage: string }) => {
  const { carouselData, isLoading, error } = useCarouselData({ sitecorePath, sitecoreLanguage });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>{carouselData.item.fields.title.value}</h2>
      <p>{carouselData.item.fields.description.value}</p>
    </div>
  );
};

Why This Approach is Useful for Sitecore Developers

Mocking Sitecore GraphQL calls offers several benefits:

  • Efficient Testing: Test components with realistic GraphQL responses, ensuring robust behavior.
  • Decoupled Development: Work independently from backend teams, accelerating your development process.
  • Comprehensive Debugging: Handle success and error scenarios easily, ensuring no edge cases are missed.

Final Thoughts on Mocking Sitecore GraphQL Calls in Storybook

Mocking GraphQL queries in Storybook with MSW is a game-changer for Sitecore developers. It allows you to efficiently develop and test components by simulating various data scenarios, from successful responses to edge cases, all without relying on live backend services. This ensures your components are robust, reliable, and ready for production.

For those working with REST APIs alongside GraphQL, don’t miss our guide on mocking REST API calls in Storybook. Combined, these tools create a powerful development workflow, enabling you to deliver high-quality Sitecore applications with confidence and efficiency.

References

Photo of Fishtank employee Sohrab Saboori

Sohrab Saboori

Senior Full-Stack Developer

Sohrab is a Senior Front-End Developer with extensive experience in React, Next.js, JavaScript, and TypeScript. Sohrab is committed to delivering outstanding digital solutions that not only meet but exceed clients' expectations. His expertise in building scalable and efficient web applications, responsive websites, and e-commerce platforms is unparalleled. Sohrab has a keen eye for detail and a passion for creating seamless user experiences. He is a problem-solver at heart and enjoys working with clients to find innovative solutions to their digital needs. When he's not coding, you can find him lifting weights at the gym, pounding the pavement on the run, exploring the great outdoors, or trying new restaurants and cuisines. Sohrab believes in a healthy and balanced lifestyle and finds that these activities help fuel his creativity and problem-solving skills.

Second CTA Ogilvy's Legacy

Today, David Ogilvy's influence can still be felt in the world of advertising.

Ogilvy's Influence Example
Emphasis on research Market research is a crucial part of any successful advertising campaign
Focus on headlines A strong headline can make the difference between an ad that is noticed and one that is ignored
Use of visuals Compelling images and graphics are essential for capturing audience attention