Mocking Magic: Unleashing Frontend Development with Apollo Server Schema Mocks

Mocking Magic: Unleashing Frontend Development with Apollo Server Schema Mocks

Accelerate Your Development Cycle and Enhance Productivity Without Waiting on the Backend

·

7 min read

In this blog post, we'll explore the art of mocking Apollo Server Schemas. Adopting a GraphQL-First development strategy is a game-changer for front-end developers. It empowers you to forge ahead with creating UI components and features seamlessly, all without being held back by backend development timelines. Let's dive into how this approach can accelerate your development process and enhance your productivity. All code produced in this post can be found here: https://github.com/maxshugar/apollo-server-mocking-example

The Necessity of Mocking in Development

Mocking in software development, particularly in the context of GraphQL and Apollo Server, is not just a convenience; it's often a necessity. Why? Because it directly addresses several pain points developers face, especially when working on the front end independent of backend progress.

Real-World Scenarios Where Mocking Shines:

  1. Early Stage Development: Imagine you're working on a new feature that relies on data from a service not yet implemented. Without mocking, your progress stalls. You're stuck waiting for the backend team to catch up, which could delay the entire project. With mocking, you can simulate the backend, proceed with development, and refine the UI and user experience, all without any backend dependency.

  2. Testing Edge Cases: Testing how your application handles edge cases or error conditions can be challenging without a fully controlled environment. For example, how does your app behave when it receives incomplete data or none at all? Setting up these scenarios with a real backend can be cumbersome and time-consuming. Mocking allows you to simulate these conditions easily, ensuring your app is robust and handles various data states gracefully.

  3. Continuous Integration (CI) and Continuous Deployment (CD): CI/CD pipelines thrive on automation. Requiring a live backend for front-end tests can introduce flakiness and dependencies that slow down these processes. Mocks ensure that your automated tests are self-contained, leading to faster, more reliable builds.

  4. Development and Testing in Parallel: When frontend and backend development occur simultaneously, the API contract might change, leading to integration issues. Mocking based on agreed contracts allows frontend development to proceed with a stable, predictable API, reducing integration headaches down the line.

Having outlined the critical role mocking plays in overcoming common development challenges, let's now transition into the practical aspects of how you can implement this approach in your projects.

Step 0: Installing Dependencies

In this tutorial, we will be building a book management app using React Native Expo, although the same steps apply to a React project.

npm i @apollo/client @graphql-tools/mock @graphql-tools/schema

Step 1: Defining our Type Definitions

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

const typeDefs = gql`
  type Book {
    title: String!
    publishedAt: String!
  }

  input AddBookInput {
    title: String!
    publishedAt: String!
  }

  type Query {
    getBooks: [Book]
  }

  type Mutation {
    addBook(input: AddBookInput): ID!
  }
`;

Let's assume we're working on a simple application that needs to manage a list of books. Our GraphQL schema includes operations for fetching books and creating a new book. With these type definitions, we can simulate real interactions with our GraphQL server without actually needing the server to be running or waiting on its implementation.

Step 2: Defining our Mocks

import { MockList } from "@graphql-tools/mock";

const mocks = {
  Book: () => ({
    title: () => "Book Title",
    publishedAt: () => "2021-01-01",
  }),
  Query: () => ({
    getBooks: () => [
      { title: "Title 1", publishedAt: "2021-01-01" },
      { title: "Title 2", publishedAt: "2021-01-02" },
      { title: "Title 3", publishedAt: "2021-01-03" },
      { title: "Title 4", publishedAt: "2021-01-04" },
      { title: "Title 5", publishedAt: "2021-01-05" },
    ],
  }),
  Mutation: () => ({
    addBook: () => "123",
  }),
};

The mocks object defines how the data for different types in the GraphQL schema should be resolved. The getBooks query returns an array of type Book. The addBooks mutation mocks the return type ID which is serialised as a string. To enhance the realism and variability of the mocked data, we could integrate a faker library, although that is out of scope for this tutorial.

Step 3: Creating the Schema

import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";

const schema = makeExecutableSchema({ typeDefs });
export const schemaWithMocks = addMocksToSchema({ schema, mocks });

We take our GraphQL type definitions (typeDefs) and use makeExecutableSchema to create an executable schema. We then use addMocksToSchema to add our mock data to our executable schema. This step involves taking the schema we created and injecting it with mock data based on the mocks object we defined earlier.

Step 4: Creating the Apollo Client

import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { SchemaLink } from "@apollo/client/link/schema";
import { schemaWithMocks } from "./schema";

const API_URL = process.env.EXPO_PUBLIC_API_URL;
const httpLink = createHttpLink({ uri: API_URL });
const schemaLink = new SchemaLink({ schema: schemaWithMocks });

export const apolloClient = new ApolloClient({
  link: API_URL ? httpLink : schemaLink,
  cache: new InMemoryCache(),
});

The API_URL variable represents the endpoint of a live GraphQL API loaded in from an environment variable. This setup is particularly useful for development and testing, allowing us to switch seamlessly between a live backend and a mocked environment without changing the client-side code. The Apollo Link library helps you customize the flow of data between Apollo Client and your GraphQL server. If API_URL has been set, we use HttpLink which is a terminating link that sends a GraphQL operation to a remote endpoint over HTTP. If the API_URL has not been set, we make use of a SchemaLink, which allows you to perform GraphQL operations on a provided schema.

Step 5: Apollo Provider

import { ApolloProvider } from '@apollo/client';
import { AddBookButton } from './src/components/addBookButton';
import { BookList } from './src/components/bookList';
import { apolloClient } from './src/gql/client';

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

Next, we must wrap our application in an ApolloProvider, which places Apollo Client in the context, enabling you to access it from anywhere in your component tree. In the examples below, I have defined two components which consume the query and mutation we defined earlier.

Step 6: Querying our Schema

import { useQuery } from "@apollo/client";
import { StyleSheet, Text, View } from "react-native";
import { gql } from "@apollo/client";

const GET_BOOKS_QUERY = gql`
  query GetBooks {
    getBooks {
      title
      publishedAt
    }
  }
`;

export const BookList = () => {
    const { loading, error, data } = useQuery(GET_BOOKS_QUERY);
    if (loading) return <Text>Loading...</Text>;
    if (error) return <Text>Error: {error.message}</Text>;
    return (
        <View>
            {data.getBooks.map((book: { title: string; publishedAt: string }, index: number) => (
                <View key={index} style={styles.container} >
                    <Text>Title: {book.title}</Text>
                    <Text>Published At: {book.publishedAt}</Text>
                </View>
            ))}
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        justifyContent: 'center',
        alignItems: 'flex-start',
        backgroundColor: '#d3d3d3',
        borderColor: 'blue',
        borderWidth: 2,
        margin: 5
    }
});

Our BookList component makes use of the useQuery hook to retrieve a list of books using the GET_BOOKS_QUERY query. Our schema link will intercept this request and generate an array of Books using the mocks we defined earlier.

Step 7: Mutating Our Schema

import { useMutation } from "@apollo/client";
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { gql } from "@apollo/client";

const ADD_BOOK_MUTATION = gql`
  mutation AddBook($input: AddBookInput!) {
    addBook(input: $input)
  }
`;

export const AddBookButton = () => {
    const [addBook, { data, loading, error }] = useMutation(ADD_BOOK_MUTATION);

    console.log({ data, loading, error })

    return (
        <TouchableOpacity
            style={{ padding: 10, backgroundColor: "lightblue", justifyContent: "center", alignItems: "center" }}
            onPress={() => {
                addBook({
                    variables: {
                        input: {
                            title: "New Book",
                            publishedAt: new Date().toISOString(),
                        }
                    },
                });
            }}
        >
            <Text>Add Book</Text>
        </TouchableOpacity>
    );
};

Our AddBookButton component initiates our ADD_BOOK_MUTATION mutation via the useMutation hook. When the button is clicked, the mutation is invoked, and the response is printed to the console. When working with a live API, we may want to re-fetch the BookList query data, or return the whole book from the mutation and update the cache (API best practices are not covered in the article).

Step 8: Live Integration with an Apollo Server

When you are ready to begin integrating your app with your Apollo server, set your EXPO_PUBLIC_API_URL environment variable to the URL of your GraphQL server. If you are developing a react application, your environment variable may look something like REACT_APP_API_URL due to reacts reacts required envionment variable prefix. Just make sure to update the file where we define our Apollo client to make use of this variable. This will cause the Apollo Client to use the http link which can establish HTTP connections with our server.

Conclusion:

In this post, we’ve explored how mocking Apollo Server schemas can dramatically enhance front-end development efficiency. By adopting a GraphQL-first strategy, developers can work independently of the backend, accelerating workflows and improving productivity. This approach allows for seamless integration with backend services, whether they are fully implemented or still evolving.

As your system requirements change, so can the API, without hindering the front-end development process. The key is fostering clear communication between UX, front-end, and back-end teams to ensure a well-defined API design. With schema mocking, front-end developers are no longer tethered to backend progress, enabling faster prototyping and delivery times.

Ultimately, the success of GraphQL-first development hinges on effectively leveraging these tools to build dynamic, robust, and user-centric applications. I encourage you to share your feedback, questions, and experiences—let’s continue learning and improving together. Happy coding!