Mocking GraphQL response for React components using MockedProvider

Mocking GraphQL response for React components using MockedProvider

In GraphQL-powered React apps, testing component behavior based on GraphQL data is a common scenario. Let's see how to achieve that with MockedProvider.

Usually, you have to consider three cases for such components and their tests:

  • Loading

  • Success

  • Error

But you also want to avoid calling the real endpoint, which in our case, is https://swapi-graphql.netlify.app/.netlify/functions/index. This is a public GraphQL endpoint returning data about Star Wars movies.

What you don't want to avoid is checking out my newsletter! I returned to part-time freelancing in February. Besides my musings about content, I also want to share some of my strategies for finding the first clients and effectively closing some jobs after being inactive for six years. So almost starting fresh.

Now back to our application!

Movie App setup

Here's what the app looks like in the Browser:

The entire app is in Codesandbox: https://codesandbox.io/s/test-react-components-with-mocked-graphql-data-89bnlp?file=/src/FilmsPage.test.tsx.

Share it with anyone or create a fork to play with it.

In this application the App.tsx component is where the ApolloProvider is being set up:

import "./styles.css";
import FilmsPage from "./FilmsPage";
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
  uri: "https://swapi-graphql.netlify.app/.netlify/functions/index",
  cache: new InMemoryCache()
});

export default function App() {
  return (
    <ApolloProvider client={client}>
      <FilmsPage />
    </ApolloProvider>
  );
}

And the FilmsPage.tsx is the component running the query using useQuery from @apollo/client:

import { gql, useQuery } from "@apollo/client";

export const GET_FILMS = gql`
  {
    allFilms {
      edges {
        node {
          id
          title
        }
      }
    }
  }
`;

export default function FilmsPage() {
  const { loading, error, data } = useQuery(GET_FILMS);

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

  return (
    <div className="App">
      <h1>Star Wars movies</h1>
      <ul>
        {data.allFilms.edges.map(
          ({ node }: { node: { id: string; title: string } }) => (
            <li key={node.id}>{node.title}</li>
          )
        )}
      </ul>
    </div>
  );
}

Testing objectives

Our goal is to test all three component states:

  • Loading

  • Error: ...

  • Success: movies are in the response, and we render them

To prepare such a test, start by creating a file next to FilmsPage.tsx component and name it FilmsPage.test.tsx. First, we will test the Errors state and, implicitly, the Loading state. You'll see why.

Error

The first and most important part of this test is about setting up the MockedProvider:

    const mocks = [
      {
        request: {
          query: GET_FILMS
        },
        error: new Error("Something went wrong")
      }
    ];

    render(
      <MockedProvider mocks={mocks}>
        <FilmsPage />
      </MockedProvider>
    );

MockedProvider mocks ApolloProvider and instead of sending an actual network request, it lets you specify the exact response payload for a given GraphQL operation.

In the above example, the response we specified is an error response.

We expect the if (error) return <p>Error: {error.message}</p>; branch of our component to run and render the error message.

This is the only thing we want to test for this scenario:

    await waitFor(() =>
      expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
    );
    expect(screen.getByText("Error: Something went wrong")).toBeInTheDocument();

Check out the entire file in the Codesandbox.

Because we wait for the Loading state to disappear, we also implicitly test the Loading state with this test.

Let's write a test for the success state.

Success

This time the mock will look different. Instead of error, we will supply a result object containing a data key.

I won't include the mockData here because it's rather long. The only thing you must be careful about is that the response you're mocking must match the structure of the response you're getting from the actual API.

    const mocks = [
      {
        request: {
          query: GET_FILMS
        },
        result: {
          data: mockData
        }
      }
    ];

    render(
      <MockedProvider mocks={mocks}>
        <FilmsPage />
      </MockedProvider>
    );

In the test, similarly to the error case, first, we will wait for the Loading... message to disappear, then we'll look at the screen and check if the movies we returned in the fake response appeared:

    await waitFor(() =>
      expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
    );
    expect(screen.getByText("Luke Skywalker in Portugal")).toBeInTheDocument();
    expect(screen.getByText("Darth Vader singing lessons")).toBeInTheDocument();

Conclusion

That's it!

You just tested your React component by providing some fake GraphQL data to it with the help of MockedProvider.

To learn more about Apollo GraphQL, check out their excellent developer documentation, where you will find super-detailed tutorials.

As always, thanks for reading my blog, and see you at the next one.

Bye 👋

Did you find this article valuable?

Support Ákos Kőműves by becoming a sponsor. Any amount is appreciated!