# Testing React components

> For a directory of all Apollo GraphQL documentation pages, see https\://www\.apollographql.com/docs/llms.txt. Adding .md to any docs URL returns the markdown version of that page.

This article describes best practices for testing React components that use Apollo Client.

The examples below use [Jest](https://facebook.github.io/jest/docs/en/tutorial-react.html) and [React Testing Library](https://github.com/testing-library/react-testing-library), but the concepts apply to any testing framework.

## The `MockedProvider` component

Every test for a React component that uses Apollo Client must make Apollo Client available on [React's context](https://react.dev/reference/react/useContext). In application code, you achieve this by wrapping your component tree with the `ApolloProvider` component. In your tests, you use the `MockedProvider` component instead.

The `MockedProvider` component enables you to define mock responses for individual queries that are executed in your test. This means your test *doesn't* need to communicate with a GraphQL server, which removes an external dependency and therefore improves the test's reliability.

### Example

Let's say we want to test the following `Dog` component, which executes a basic query and displays its result:

```jsx title=dog.jsx
import React from "react";
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

// Make sure that both the query and the component are exported
export const GET_DOG_QUERY = gql`
  query GetDog($name: String) {
    dog(name: $name) {
      id
      name
      breed
    }
  }
`;

export function Dog({ name }) {
  const { loading, error, data } = useQuery(GET_DOG_QUERY, {
    variables: { name },
  });
  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error.message}</p>;
  return (
    <p>
      {data.dog.name} is a {data.dog.breed}
    </p>
  );
}
```

A basic rendering test for the component looks like this (minus mocked responses):

```jsx title=dog.test.js
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { MockedProvider } from "@apollo/client/testing/react";
import { GET_DOG_QUERY, Dog } from "./dog";

const mocks = []; // We'll fill this in next

it("renders without error", async () => {
  render(
    <MockedProvider mocks={mocks}>
      <Dog name="Buck" />
    </MockedProvider>
  );
  expect(await screen.findByText("Loading...")).toBeInTheDocument();
});
```

> **Note:** Usually, you import `@testing-library/jest-dom` in your [test setup file](https://jestjs.io/docs/configuration), which provides certain [custom jest matchers](https://github.com/testing-library/jest-dom#custom-matchers) (such as `toBeInTheDocument`). The import is included in these examples for completeness.

#### Defining mocked responses

The `mocks` prop of `MockedProvider` is an array of objects, each of which defines the mock response for a single operation. Let's define a mocked response for `GET_DOG_QUERY` when it's passed the `name` `Buck`:

```jsx title=dog.test.js
const mocks = [
  {
    request: {
      query: GET_DOG_QUERY,
      variables: {
        name: "Buck",
      },
    },
    result: {
      data: {
        dog: { __typename: "Dog", id: "1", name: "Buck", breed: "bulldog" },
      },
    },
  },
];
```

Each mock object defines a `request` field (indicating the shape and variables of the operation to match against) and a `result` field (indicating the shape of the response to return for that operation).

> **Your test must execute an operation that *exactly* matches a mock's shape *and* variables to receive the associated mocked response.**

Alternatively, the `result` field can be a function that returns a mocked response after performing arbitrary logic:

```jsx
result: (variables) => { // `variables` is optional
  // ...arbitrary logic...

  return {
    data: {
      dog: { __typename: 'Dog', id: '1', name: 'Buck', breed: 'bulldog' },
    },
  }
},
```

Combining our code above, we get the following complete test:

```jsx title=dog.test.js
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import { MockedProvider } from "@apollo/client/testing/react";
import { GET_DOG_QUERY, Dog } from "./dog";

const mocks = [
  {
    request: {
      query: GET_DOG_QUERY,
      variables: {
        name: "Buck",
      },
    },
    result: {
      data: {
        dog: { __typename: "Dog", id: "1", name: "Buck", breed: "bulldog" },
      },
    },
  },
];

it("renders without error", async () => {
  render(
    <MockedProvider mocks={mocks}>
      <Dog name="Buck" />
    </MockedProvider>
  );
  expect(await screen.findByText("Loading...")).toBeInTheDocument();
});
```

#### Reusing mocks

By default, a mock is only used once. If you want to reuse a mock for multiple operations, you can set the `maxUsageCount` field to a number indicating how many times the mock should be used:

```jsx title=dog.test.js
import { GET_DOG_QUERY } from "./dog";

const mocks = [
  {
    request: {
      query: GET_DOG_QUERY,
      variables: {
        name: "Buck",
      },
    },
    result: {
      data: {
        dog: { __typename: "Dog", id: "1", name: "Buck", breed: "bulldog" },
      },
    },
    maxUsageCount: 2, // The mock can be used twice before it's removed, default is 1
  },
];
```

Passing `Number.POSITIVE_INFINITY` will cause the mock to be reused indefinitely.

### Dynamic variables

Sometimes, you don't know the exact value of the variables for a query. The `variables` property of the `MockedResponse` object accepts a callback function that takes the variables and returns a boolean indicating whether this mock should match the invocation for the provided query.

For example, this mock will match all dog queries:

```ts
import { MockedResponse } from "@apollo/client/testing";

const dogMock: MockedResponse<Data, Variables> = {
  request: {
    query: GET_DOG_QUERY,
    variables: (variables) => true,
  },
  result: {
    data: { dog: { __typename: "Dog", id: 1, name: "Buck", breed: "poodle" } },
  },
};
```

This can also be useful for asserting specific variables individually:

```ts
import { MockedResponse } from "@apollo/client/testing";

const dogMock: MockedResponse<Data, Variables> = {
  request: {
    query: GET_DOG_QUERY,
    variables: jest.fn().mockReturnValue(true),
  },
  result: {
    data: { dog: { __typename: "Dog", id: 1, name: "Buck", breed: "poodle" } },
  },
};

expect(dogMock.request.variables).toHaveBeenCalledWith(
  expect.objectContaining({
    name: "Buck",
  })
);
```

## Testing the "loading" and "success" states

To test how your component is rendered after its query completes, Testing Library provides several `findBy` methods. From the [Testing Library docs](https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries):

> `findBy` queries work when you expect an element to appear but the change to the DOM might not happen immediately.

We can use the asynchronous `screen.findByText` method to query the DOM elements containing the loading message first, followed by the success message `"Buck is a poodle"` (which appears after our query completes):

```jsx
it("should render dog", async () => {
  const dogMock = {
    delay: 30 // to prevent React from batching the loading state away
    // delay: Infinity // if you only want to test the loading state

    request: {
      query: GET_DOG_QUERY,
      variables: { name: "Buck" }
    },
    result: {
      data: { dog: { __typename: "Dog", id: 1, name: "Buck", breed: "poodle" } }
    }
  };
  render(
    <MockedProvider mocks={[dogMock]}>
      <Dog name="Buck" />
    </MockedProvider>
  );
  expect(await screen.findByText("Loading...")).toBeInTheDocument();
  expect(await screen.findByText("Buck is a poodle")).toBeInTheDocument();
});
```

## Testing with Suspense

When testing components that use `useSuspenseQuery` or other [suspense hooks](https://www.apollographql.com/docs/react/data/suspense/), wrap your React Testing Library `render` call in `await act()`.

Unlike `useQuery`, which keeps the component mounted in a loading state while the request is in flight, `useSuspenseQuery` suspends the component immediately. Without `await act()`, the component stays suspended in your test environment and the test times out.

For example, given this `Greeting` component that uses `useSuspenseQuery`:

```jsx title=greeting.jsx
import { Suspense } from "react";
import { gql } from "@apollo/client";
import { useSuspenseQuery } from "@apollo/client/react";

export const GET_GREETING_QUERY = gql`
  query GetGreeting {
    greeting {
      message
    }
  }
`;

export function Greeting() {
  const { data } = useSuspenseQuery(GET_GREETING_QUERY);
  return <div>{data.greeting.message}</div>;
}
```

Test it by wrapping `render` in `await act()`:

```jsx title=greeting.test.jsx
import "@testing-library/jest-dom";
import { Suspense } from "react";
import { act, render, screen } from "@testing-library/react";
import { MockedProvider } from "@apollo/client/testing/react";
import { GET_GREETING_QUERY, Greeting } from "./greeting";

const mocks = [
  {
    request: { query: GET_GREETING_QUERY },
    result: {
      data: {
        greeting: { __typename: "Greeting", message: "Hello" },
      },
    },
  },
];

it("should render the greeting", async () => {
  await act(() =>
    render(
      <MockedProvider mocks={mocks}>
        <Suspense fallback={<div>Loading...</div>}>
          <Greeting />
        </Suspense>
      </MockedProvider>
    )
  );

  expect(await screen.findByText("Hello")).toBeInTheDocument();
});
```

The `await act()` wrapper allows React to process the suspended component and deliver the mock response before assertions run.

## Testing error states

Your component's error states are just as important to test as its success state, if not more so. You can use the `MockedProvider` component to simulate both network errors and GraphQL errors.

* Network errors are errors that occur while your client attempts to communicate with your GraphQL server.
* GraphQL errors are errors that occur while your GraphQL server attempts to resolve your client's operation.

### Network errors

To simulate a network error, you can include an `error` field in your test's mock object, instead of the `result` field:

```jsx
it("should show error UI", async () => {
  const dogMock = {
    request: {
      query: GET_DOG_QUERY,
      variables: { name: "Buck" },
    },
    error: new Error("An error occurred"),
  };
  render(
    <MockedProvider mocks={[dogMock]}>
      <Dog name="Buck" />
    </MockedProvider>
  );
  expect(await screen.findByText("An error occurred")).toBeInTheDocument();
});
```

In this case, when the `Dog` component executes its query, the `MockedProvider` returns the corresponding error. This applies the error state to our `Dog` component, enabling us to verify that the error is handled gracefully.

### GraphQL errors

To simulate GraphQL errors, you define an `errors` field *inside* a mock's `result` field. The value of this field is an array of instantiated `GraphQLError` objects:

```js
const dogMock = {
  // ...
  result: {
    errors: [new GraphQLError("Error!")],
  },
};
```

Because GraphQL supports returning partial results when an error occurs, a mock object's `result` can include both `errors` *and* `data`.

## Testing mutations

You test components that use `useMutation` similarly to how you test components that use `useQuery`. Just like in your application code, the primary *difference* is that you need to call the mutation's **mutate function** to actually execute the operation.

### Example

The following `DeleteButton` component executes the `DELETE_DOG_MUTATION` to delete a dog named `Buck` from our graph (don't worry, Buck will be fine 🐶):

```jsx title=delete-dog.jsx
import React from "react";
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";

export const DELETE_DOG_MUTATION = gql`
  mutation deleteDog($name: String!) {
    deleteDog(name: $name) {
      id
      name
      breed
    }
  }
`;

export function DeleteButton() {
  const [mutate, { loading, error, data }] = useMutation(DELETE_DOG_MUTATION);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;
  if (data) return <p>Deleted!</p>;

  return (
    <button onClick={() => mutate({ variables: { name: "Buck" } })}>
      Click to Delete Buck
    </button>
  );
}
```

We can test the initial rendering of this component just like we [tested our `Dog` component](https://www.apollographql.com/docs/react/development-testing/testing.md#example):

```jsx title=delete-dog.test.js
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
import { MockedProvider } from "@apollo/client/testing/react";
import { DeleteButton, DELETE_DOG_MUTATION } from "./delete-dog";

it("should render without error", () => {
  render(
    <MockedProvider mocks={[]}>
      <DeleteButton />
    </MockedProvider>
  );
});
```

In the test above, `DELETE_DOG_MUTATION` is *not* executed, because the mutate function is not called.

The following test *does* execute the mutation by clicking the button:

```jsx title=delete-dog.test.js
it("should render loading and success states on delete", async () => {
  const deleteDog = { __typename: "Dog", name: "Buck", breed: "Poodle", id: 1 };
  const mocks = [
    {
      request: {
        query: DELETE_DOG_MUTATION,
        variables: { name: "Buck" },
      },
      result: { data: deleteDog },
    },
  ];

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

  // Find the button element...
  const button = await screen.findByText("Click to Delete Buck");
  userEvent.click(button); // Simulate a click and fire the mutation

  expect(await screen.findByText("Loading...")).toBeInTheDocument();
  expect(await screen.findByText("Deleted!")).toBeInTheDocument();
});
```

Again, this example is similar to [the `useQuery`-based component above](https://www.apollographql.com/docs/react/development-testing/testing.md#example), but it differs after the rendering is completed. Because this component relies on a button click to fire a mutation, we use Testing Library's [user-event](https://github.com/testing-library/user-event) library to simulate a click with its `click` method. This fires off the mutation, and the rest of the test runs as expected.

Remember that the mock's value for `result` can also be a function, so you can perform arbitrary logic (like setting a boolean to indicate that the mutation completed) before returning its result.

[Testing error states](https://www.apollographql.com/docs/react/development-testing/testing.md#testing-error-states) for mutations is identical to testing them for queries.

## Testing with the cache

If your application sets any [cache configuration options](https://www.apollographql.com/docs/react/caching/cache-configuration/#configuration-options) (such as `possibleTypes` or `typePolicies`), you should provide `MockedProvider` with an instance of `InMemoryCache` that sets the exact same options:

```jsx
const cache = new InMemoryCache({
  // ...configuration options...
})

<MockedProvider mocks={mocks} cache={cache}>
  <DeleteButton />
</MockedProvider>,
```

The following sample specifies `possibleTypes` and `typePolicies` in its cache configuration, both of which must also be specified in relevant tests to prevent unexpected behavior.

```jsx
// "Dog" supertype can be of type "ShibaInu"
const ShibaFragment = gql`
  fragment ShibaInuFields on Dog {
    ... on ShibaInu {
      tail {
        isCurly
      }
    }
  }
`;

export const GET_DOG_QUERY = gql`
  query GetDog($name: String) {
    dog(name: $name) {
      id
      name
      breed

      ...ShibaInuFields
    }
  }

  ${ShibaFragment}
`;

export const cache = new ApolloClient({
  cache: new InMemoryCache({
    possibleTypes: {
      Dog: ["ShibaInu"],
    },
    // suppose you want you key fields for "Dog" to not be simply "id"
    typePolicies: {
      keyFields: {
        Dog: ["name", "breed"],
      },
    },
  }),
});
```

## Testing local state

In order to properly test local state using `MockedProvider`, you'll need to pass a configured cache into `MockedProvider` itself.

`MockedProvider` creates its own ApolloClient instance behind the scenes like this:

```jsx
const { mocks, defaultOptions, cache, resolvers, link, showWarnings } =
  this.props;
const client = new ApolloClient({
  cache: cache || new Cache(),
  defaultOptions,
  link: link || new MockLink(mocks || [], { showWarnings }),
  resolvers,
});
```

Therefore if you're using Apollo Client 2.x local resolvers, or Apollo Client 3.x type/field policies, you have to tell the `MockedProvider` component what you're going to do with `@client` fields. Otherwise the `ApolloClient` instance created behind the scenes doesn't know how to handle your tests.

If using Apollo Client 2.x local resolvers, make sure your resolvers object is passed into `MockedProvider`:

```jsx
<MockedProvider mocks={mocks} resolvers={resolvers} ...
```

If using Apollo Client 3.x type/field policies, make sure your configured cache instance (with your typePolicies) is passed into `MockedProvider`:

```jsx
<MockedProvider mocks={mocks} cache={cache} ...
```

If you're using Apollo Client 2.x [local resolvers](https://www.apollographql.com/docs/react/local-state/local-resolvers/), you *also* need to pass your resolver map:

```jsx
<MockedProvider mocks={mocks} cache={cache} resolvers={resolvers} ...
```

This is necessary because otherwise, the `MockedProvider` component doesn't know how resolve [local-only fields](https://www.apollographql.com/docs/react/local-state/managing-state-with-field-policies/) in your queries.

## Sandbox example

For a working example that demonstrates how to test components, check out this project on CodeSandbox:
