How to mock GraphQL queries and mutations in Apollo Client

Marcin Kwiatkowski /
How to mock GraphQL queries and mutations in Apollo Client

Nowadays, distributed architectures of software have become more popular, and along with the trend, software teams use the API-first approach to building products.

On the other hand, architects and developers decide to use the GraphQL API instead of REST more and more (but REST APIs are still good for sure)

I will not cite the well-known advantages and disadvantages of GraphQL here, but I want to stand the one that is not well-known but is quite important for developers:

One of the most significant advantages of using GraphQL is that a frontend developer can quickly mock sample data, and switch to real data when the backend is done.

What exactly is mock-up data?

Suppose some functionality needs data from the backend or somehow needs to communicate with the API, and this data is not available, or the API hasn’t been done yet. In that case, the Front-end developer needs to mock up some sample data. Take a look at the examples below:

1. Displaying additional information about a product

A development team is working on displaying additional information about a product on a product page. The data are called “Key features” and consist of an image, a name, and a description. This data will come from the backend. The backend team hasn’t started working on this functionality yet, so the Frontend developer decides to mock up this data and display this mockup on the front end. When the backend is done, the mockup data will be replaced with real data.

2. Sending a message to a seller

This functionality lets customers send a message to a seller. A customer fills out the form. He enters his name, surname, e-mail address, and message. Additionally, they must accept consent to the processing of personal data. The frontend developer has already built the form and validation and is at the stage of sending the form to the backend. The backend must pick up the form and return a success or error message, and this message will be displayed to the user. The backend part is not ready yet, so the Frontend developer needs to mock this interaction with the backend.

In summary, when some data like fields or even data collections have not been done yet in backend implementation, developers use fake values (mock data) and replace them with real data when the backend is ready.

In this article, I show how to mock:

  • - single fields

  • - queries

  • - mutation

In the end, I will show you how to easily replace mock data with real backend data.

With this knowledge, you will be able to work on your front end more efficiently, even when the back end is lagging far behind.

Prerequisites

Computer with a text editor, NodeJS, internet connection, basic JavaScript, React, and GraphQL skills.

Create react app

I use create-react-app to scaffold a new project:

1npx create-react-app graphql-mocks
2cd graphql-mocks
3npm start

Install Apollo Client

Next, I use the command line to install the apollo client:

1npm install @apollo/client graphql

Apollo client allows connecting with GraphQL server and performing GraphQL operations like queries and mutations thanks to custom React hooks like useQuery or useMutation.

Initialize Apollo client

Once the client is installed, I can connect my front end with GraphQL API. This time I am going to use publically available SpaceX GraphQL API.

First, I import Apolo Client, InMemoryCache, and ApolloProvider from @apollo/client.

1import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

Second, I initialize the client:

1const client = new ApolloClient({
2    uri: 'https://api.spacex.land/graphql/',
3    cache: new InMemoryCache(),
4});

That is the minimal config needed to make Apollo Client working - URL to API and in-memory cache initialized.

Third, I wrap the App component by ApolloProvider:

1<ApolloProvider client={client}>
2  <App />
3</ApolloProvider>

ApolloProvided takes one argument: a client that we have already initialized. Once I wrap the App component with ApolloProvider, I can use the client in the App component and every child of the app component.

How to mockup single fields

In this example, I show you how to fetch existing fields from the API and how to add a field that doesn't exist in the response.

Create the missions component

In the beginning, I create the Missions component that will be responsible for displaying missions.

Create components \ missions \ Missions.jsx file:

1 export const Missions = () => {
2    return <>
3        <h1>Missions</h1>
4        Missions will be listed here
5    </>
6}

Create components \ missions \ index.js file:

1export { Missions } from './Missions';

Create components \ index.js file:

1export { Missions } from './missions';

Import Missions component in the App.js:

1import { Missions } from "./components";

Render component:

1function App() {
2  return <main className="container">
3    <Missions/>
4  </main>
5}

Add styles to App.css:

1.container {
2  max-width: 1200px;
3  margin: 0 auto;
4}

The component in the browser should look like this:

mock api tutorial

The query for the data

I want to fetch missions from the graphql server. To do so, I need to define a query.

I add file components \ missions\ missions.gql.js:

1import {gql} from "@apollo/client";
2
3export default gql`{
4  missions(limit: 10) {
5    description
6    id
7    manufacturers
8    name
9    twitter
10    website
11    wikipedia
12  }
13}
14`;

I import the query and useQuery hook in components \ Missions \ missions.jsx:

1import {useQuery} from "@apollo/client";
2import MISSIONS_QUERY from './missions.gql.js'

I use the useQuery hookto fetch graphql data:

1const {loading, error, data} = useQuery(MISSIONS_QUERY);

I add a little logic to handle loading and errors state:

1if (loading) return null;
2if (error) return `Error! ${error}`
3if (!data?.missions?.length) {
4    return 'No missions found';
5}
6
7const {missions} = data;
8

Finally, I render data returned from API:

1{missions.map(mission => {
2    return <div className="mission" key={mission.id}>
3        <h2>{mission.name}</h2>
4        <p>{mission.description}</p>
5        <h3>Manufacturers:</h3>
6        <ol>
7            {mission.manufacturers?.map(manufacturer => <li key={`${mission.id}-${manufacturer}`}>{manufacturer}</li>)}
8        </ol>
9        <h3>Links:</h3>
10        <ul>
11            {mission.twitter?.length && <li><a href={mission.twitter}>{mission.twitter}</a></li> }
12            {mission.website?.length && <li><a href={mission.website}>{mission.website}</a></li> }
13            {mission.wikipedia?.length && <li><a href={mission.wikipedia}>{mission.wikipedia}</a></li> }
14        </ul>
15    </div>
16})}

The complete code of the component looks like this:

1import {useQuery} from "@apollo/client";
2import MISSIONS_QUERY from './missions.gql.js'
3
4export const Missions = () => {
5    const {loading, error, data} = useQuery(MISSIONS_QUERY);
6
7    if (loading) return null;
8    if (error) return `Error! ${error}`
9    if (!data?.missions?.length) {
10        return 'No missions found';
11    }
12
13    const {missions} = data;
14
15    return <>
16        <h1>Missions</h1>
17        {missions.map(mission => {
18            return <div className="mission" key={mission.id}>
19                <h2>{mission.name}</h2>
20                <p>{mission.description}</p>
21                <h3>Manufacturers:</h3>
22                <ol>
23                    {mission.manufacturers?.map(manufacturer => <li key={`${mission.id}-${manufacturer}`}>{manufacturer}</li>)}
24                </ol>
25                <h3>Links:</h3>
26                <ul>
27                    {mission.twitter?.length && <li><a href={mission.twitter}>{mission.twitter}</a></li> }
28                    {mission.website?.length && <li><a href={mission.website}>{mission.website}</a></li> }
29                    {mission.wikipedia?.length && <li><a href={mission.wikipedia}>{mission.wikipedia}</a></li> }
30                </ul>
31            </div>
32        })}
33    </>
34}

You should see rendered data in the browser:

data fetched from graphql server rendered on fronted

Extend GraphQL schema on the client side

Here, the main topic of the article begins! The Missions component renders some data about missions on the front like description, name, web links and so on:

schema defined in graphql

I want to mock one single field, which is a sponsors field, and it is an array of strings. To do so, I create the graphql-type-defs.js file in the root of the project with the following content:

1import {gql} from "@apollo/client";
2
3export default gql`
4    extend type Mission {
5        sponsors: [String]
6    }
7`;

That schema definition extends the Mission type and ads sponsors field to it.

Now I import my type definition in the index.js file:

1import typeDefs from './graphql-type-defs';

And pass it to the ApolloClient constructor:

1const client = new ApolloClient({
2    uri: 'https://api.spacex.land/graphql/',
3    cache: new InMemoryCache(),
4    typeDefs
5});

Define a read function with mock data

The Next step is to define a custom read function to produce mock data for us.

Before I do that, I install FakerJS. it's a library (fake data generator) that helps produce fake and random data.

1npm install @faker-js/faker --save-dev

Then, I pass the configuration with object types policies to the InMemoryCache constructor:

1cache: new InMemoryCache({
2    typePolicies: {
3        Mission: {
4            fields: {
5                sponsors: {
6                    read() {
7                        return [...faker.random.words(faker.datatype.number({
8                            'min': 1,
9                            'max': 5
10                        })).split(' ')]
11                    }
12                },
13            },
14        },
15    },
16}),

That code defines the read() function for the sponsors' field of the Mission type. The read() function returns fake objects. In this case, it returns a new array of from one to five elements. Elements in that array are random words.

Query with the @client directive and display data

To fetch the mock field, I need to add it to the query. To make it work, I need to use the @client directive. Take a look at the updated missions query:

1export default gql`{
2  missions(limit: 10) {
3    description
4    id
5    manufacturers
6    name
7    twitter
8    website
9    wikipedia
10    sponsors @client // here you go
11  }
12}
13`;

Finally, I can render the sponsors field on the front end. I add this code to the render function of the missions component:

1<h3>Sponsors:</h3>
2<ol>
3    {mission.sponsors?.map(sponsor => <li key={`${mission.id}-${sponsor}`}>{sponsor}</li>)}
4</ol>

Results in the browser:

mock objects on the screen

How to mockup an entire query

Mocking single fields is so useful. Moreover, sometimes devs want to mock a query or mutation that doesn't exist in the backend. Let's start by mocking a query.

Add a new query to the schema

Let's add the publications query that returns an array of publications (name of the publication and URL).

I extend the graphql-type-defs.js by adding new types:

1type Query {
2        publications: [Publication]
3    }
4  
5    type Publication {
6        name: String!
7        url: String!
8    }

Define resolver

Next, I need to define a resolver that will produce fake data for the publications query.

I create a graphql-resolvers.js file:

1import {faker} from "@faker-js/faker";
2
3export default {
4    Query: {
5        publications: () => {
6            const publications  = [];
7            const publicationLength = faker.datatype.number({
8                'min': 1,
9                'max': 5
10            })
11
12            for (let i = 0; i < publicationLength; i++) {
13                publications.push({
14                    name: faker.lorem.sentence(),
15                    url: faker.internet.url()
16                })
17            }
18            return publications;
19        },
20    }
21}

I defined the publications function that returns a new array of fake publications.

Register resolver

To register the resolver, you need to pass it to the ApolloClient constructor:

1import resolvers from './graphql-resolvers';
2
3const client = new ApolloClient({
4    uri: 'https://api.spacex.land/graphql/',
5    cache: new InMemoryCache({
6        typePolicies: {
7            Mission: {
8                fields: {
9                    sponsors: {
10                        read() {
11                            return [...faker.random.words(faker.datatype.number({
12                                'min': 1,
13                                'max': 5
14                            })).split(' ')]
15                        }
16                    },
17                },
18            },
19        },
20    }),
21    typeDefs,
22    resolvers // here you go
23});

Use mocked query in the app

Let's create the publications component that displays mocked data.

components \ publications \ Publications.jsx

1import {useQuery} from "@apollo/client";
2// 1. here is imported the publications query
3import PUBLICATIONS_QUERY from './publications.gql.js'
4
5export const Publications = () => {
6   // 2. here the query is used
7   const {loading, error, data} = useQuery(PUBLICATIONS_QUERY);
8
9
10    if (loading) return null;
11    if (error) return `Error! ${error}`;
12    if (!data?.publications?.length) {
13        return 'No publications found';
14    }
15
16    const {publications} = data;
17
18    return <>
19        <h1>Publications</h1>
20        <ol>
21            {publications?.map(publication => <li key={publication.name}><a href={publication.url}>{publication.name}</a></li>)}
22        </ol>
23    </>
24}

(1.) I imported the publications query in this place, and here (2.) I used in it the useQuery hook.

As you can see from the component and useQuery hook perspective, it's not important if the quarry that is used is fake or real. It's transparent and works in the same way.

components \ publications \ publications.gql.js:

1import {gql} from "@apollo/client";
2
3export default gql`
4    {
5        publications @client {
6            name
7            url
8        }
9    }
10`;

Directive @client allows defining not only fields like in the previous example but also queries and mutations.

components \ publications \ index.js:

1export {Publications} from './Publications'

components \ index.js:

1export { Publications } from './publications'; // added this import
2export { Missions } from './missions';

Add the publication component in the App.js:

1import './App.css';
2import {Missions, Publications} from "./components"; // added import
3
4function App() {
5  return <main className="container">
6    <Missions/>
7    <Publications/> !<-- added component -->
8  </main>
9}
10
11export default App;

Results in the browser:

mock object on the screen

Ho tomock a graphql mutation

The last example I want to show is how to mock a graphql mutation. Let's implement a simple form that allows users to submit a new publication. The form has two inputs: the title of the publication and its URL.

form ui components

Add the PublicationForm component

Create a file components \ PublicationForm \ PublicationForm.jsx

1import {useCallback, useState} from "react";
2
3export const PublicationForm = () => {
4    const [ title, setTitle ] = useState('');
5    const [ url, setUrl ] = useState('');
6
7    const submitForm = useCallback((e) => {
8        e.preventDefault();
9        console.log(title, url);
10    }, [title, url]);
11    return <form onSubmit={submitForm}>
12        <legend>Submit a new publication:</legend>
13        <input type="text" placeholder="Publication title" value={title} onChange={(e) => setTitle(e.target.value)}/>
14        <input type="text" placeholder="Publication URL" value={url} onChange={(e) => setUrl(e.target.value)}/>
15        <button type="submit">Submit</button>
16    </form>
17}

So there are two fields in the form and the submit button. When a user clicks submit, the submitForm function is called. For now, it only logs to the console.

Create a file components \ publicationForm \ index.js

1export { PublicationForm } from './PublicationForm';

Re-export component in components/index.js:

1export { Publications } from './publications';
2export { Missions } from './missions';
3export { PublicationForm } from './publicationForm'; // added here

Add the component to the render function of the app component:

1import './App.css';
2import {Missions, Publications, PublicationForm} from "./components";
3
4function App() {
5  return <main className="container">
6    <Missions/>
7    <Publications/>
8    <PublicationForm/>
9  </main>
10}
11
12export default App;

Add the mutation to the schema

Let's define a new mutation in our graphql schema:

1type Mutation {
2  addPublication(name: String!, url: String!): String
3}

The final graphql-type-defs looks like this:

1import {gql} from "@apollo/client";
2
3export default gql`
4    extend type Mission {
5        sponsors: [String]
6    }
7    
8    type Query {
9        publications: [Publication]
10    }
11    
12    type Mutation {
13      addPublication(name: String!, url: String!): String
14    }
15  
16    type Publication {
17        name: String!
18        url: String!
19    }
20`;

Define a resolver for the mutation

Now, I am gonna add the addPublication mutation resolver to the graphql-resolvers.js file:

1import {faker} from "@faker-js/faker";
2
3export default {
4    Query: {
5        // query resolvers
6    },
7
8    Mutation: {
9        addPublication: (parent, args, context, info) => {
10            console.log(parent, args, context, info);
11            return 'Your publication has been submitted, thank you!'
12
13        }
14    }
15}

I defined the mutation, and it returns a string. Of course, if you need more sophisticated testing of mocked mutation, you can add code here.

How to use mocked mutation in the app

Add components \ publicationForm \ addPublication.gql.js file:

1import {gql} from "@apollo/client";
2
3export default gql`
4    mutation addPublication(
5        $name: String!,
6        $url: String!
7    ) {
8        addPublication(
9            name: $name,
10            url: $url
11        ) @client
12    }
13`;

As you can see, here also I used the @client directive to define mock mutation

Update the publicationForm.jsx component code:

1import {useCallback, useMemo, useState} from "react";
2import {useMutation} from "@apollo/client";
3import ADD_PUBLICATION_MUTATION  from './addPublication.gql';
4
5export const PublicationForm = () => {
6    const [ name, setName ] = useState('');
7    const [ url, setUrl ] = useState('');
8    const [addPublication, {data, loading, error}] = useMutation(ADD_PUBLICATION_MUTATION)
9
10    const submitForm = useCallback((e) => {
11        e.preventDefault();
12        addPublication({variables: {name, url }});
13    }, [name, url, addPublication]);
14
15    const results = useMemo(() => {
16        return data?.addPublication
17    }, [data]);
18
19    if (loading) return null;
20    if (error) return `Error! ${error}`;
21
22    return <form onSubmit={submitForm}>
23        <legend>Submit a new publication:</legend>
24        <input type="text" placeholder="Publication title" value={name} onChange={(e) => setName(e.target.value)}/>
25        <input type="text" placeholder="Publication URL" value={url} onChange={(e) => setUrl(e.target.value)}/>
26        <button type="submit">Submit</button>
27        <div>{results}</div>
28    </form>
29}

Here I added the logic responsible for performing mutation. As for queries - it work the same with mocked mutation as with real ones.

Results in the browser:

fake graphql api

How to use live data when it is ready

Ok, so we have mocked some fields, queries, and mutations, and you may ask what you should do when the backend team implements all requested fields and operations in the API.

It's pretty simple. You should:

  1. 1. remove @client annotations - when a particular field or operation is ready, just remove the @client directive from query/mutation

  2. 2. remove client resolvers - remove resolvers because they are not needed anymore when data is populated from API

  3. 3. remove client type definitions - same here, the schema should be implemented on the backend side, so it's no need anymore

Summary

In this article, I showed you how to mock GraphQL queries and mutations. Compared to a REST API, mocking GraphQL queries is much easier. The subsequent transition to real data only really involves a change in GraphQL queries and resolvers’ removal. In my opinion, mocking data in GraphQL is much easier than in REST, which is unquestionably beneficial for everyone.

When you want to mock some fields or operations on the client side using Apollo Client, please follow these steps:

  1. 1. Create client-side GraphQL schema

  2. 2. Define custom resolvers/read function

  3. 3. Use @client directive in queries/mutations

I hope you liked this article. Thanks for reading!

Share this article with your friends!

Each share supports and motivates me to create new content. Thank you!

About the author

Marcin Kwiatkowski

Frontend developer, Certified Magento Full Stack Developer, writer, based in Poland. He has eight years of professional Software engineering experience. Privately, husband and father. He likes reading books, drawing, and walking through mountains.

Read more