GraphQL is a query language for APIs. Using such queries has a lot of advantages. Take a look below at the two most important:

1. GraphQL queries always return predictable results

When you send a query, you describe what you want, and in response, you get precisely what you wanted.

2. In one query, you can get many resources

You can get all the data you need in one query. Thanks to this, it is not necessary to send queries to different URLs to collect all the necessary data.

GraphQL is becoming more and more popular, and there is a trend to replace REST APIs with an API built to use GraphQL. Such a change is taking place in Magento, and PWA Studio is an excellent example of using a GraphQL API.

In one of my recent articles, I wrote about another GraphQL advantage, one which is significant from the Development Team’s perspective:

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 Frontend 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 frontend. 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, 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.

3.Resolving programmer’s issues

A frontend developer is also a programmer, right? Exactly, and programmers sometimes have problems, especially issues with code, and the most serious, most common difficulty is:

My code does not work, and I don’t know why 🙁

In this case, it’s good to talk to another developer about the problem and find a solution together. Unfortunately, sometimes there is no other programmer nearby because, for example, you work in a different time zone, or you do not let anyone into your garage where you are writing a new web application for the government. In this case, you need a programmer mockup, that is:

Duck as a mockup of programmer
Rubber duck debugging!

Oh yes, the rubber duck can help too, and let those who have never used its help throw the first stone! 😉

GraphQL data mockup

Today, I will show you how to mock up data and interactions with a GraphQL API on a simple module for PWA Studio. Our module will display the mocked data on the storefront. It will also be possible to send a form. I will show you how to easily mock up:

  • a GraphQL query
  • a GraphQL mutation

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

Module skeleton

First, we will create a skeleton of our module. I will not elaborate on this topic because I have already done it here. If you are not familiar with the PWA Studio Extensibility Framework, be sure to read this article first.

Create a directory for the module with empty index.js file

$ mkdir mock-component-plugin
$ cd mock-component-plugin
$ touch index.js

Package.json file

{
  "name": "@marcinkwiatkowski/mock-component-plugin",
  "version": "1.0.2",
  "description": "PWA Studio Component which mocked GraphQL data",
  "author": "Marcin Kwiatkowski <contact@frodigo.com>",
  "license": "MIT",
  "pwa-studio": {
    "targets": {
      "intercept": "intercept.js"
    }
  },
  "peerDependencies": {
    "@magento/pwa-buildpack": "*",
    "@magento/venia-ui": "*",
    "react": "~16.9.0",
    "react-router-dom": "~5.1.0"
  }
}

Intercept.js file

module.exports = targets => {
    targets.of('@magento/pwa-buildpack').specialFeatures.tap(flags => {
        flags[targets.name] = {esModules: true, cssModules: true};
    });
}

Mocking GraphQL operations

We will start with the most important file for this article’s topic, i.e., mocking GraphQL data.

Create lib/queries/mocked-operations.gql.js file

$ mkdir -p lib/queries
$ touch lib/queries/mocked-operations.gql.js

Now open this file in your editor. We will successively add new elements to it.

Import graphql-tag library

import gql from "graphql-tag";

graphql-tag is a library that parses GraphQL queries and allows you to use them in JS.

Import Faker.js

import faker from "faker/locale/en";

Faker.js is a library that will help mock data.

Add the query

const GET_MOCKED_DATA = gql`
    query getMockedData {
        mocked_data @client {
            id
            name
            description
        }
    }
`;

We just made our first query that will get fake data. The key here is line 3. On this line, we point to the name mocked_data, which we will refer to later on. Adding the @client flag here is necessary for the API to know that it shouldn’t send this query to the backend, but look for a mockup on the client-side (frontend). In the fourth to the sixth lines, we define what data our query is to return. In this case, it’s ID, name, and description.

Add the mutation

Now we will add a mutation called addMockedItem

const ADD_MOCKED_ITEM_MUTATION = gql`
    mutation addMockedItem(
        $name: String,
        $description: String
    ) {
        addMockedItem(
            name: $name,
            description: $description
        ) @client
    }
`;

The mutation takes two parameters: name and description. This data will come from the form that the user completes. As you can see on line 9, we added the @client flag, which means that this mutation will only be processed on the client-side.

Export query and mutation

Now we will export our query and mutation as an object to use them later in the app.

export default {
    mutations: {
        addMockedItem: ADD_MOCKED_ITEM_MUTATION
    },
    queries: {
        getMockedData: GET_MOCKED_DATA
    }
};

Define resolver

Time to define the resolver. This is the object where we define our mocked queries and the GraphQL mutations.

export const mockedDataResolvers = {
    Query: {
        // here is the place for mocked queries
    },
    Mutation: {
        // here is the place for mocked mutations
    }
};

Mock query

We will add to our query, which we defined in the previous steps as mocked_data, to the resolver.

export const mockedDataResolvers = {
    Query: {
        mocked_data: () => {
            let mockedData = [];
            const RANDOM_LENGTH = faker.random.number({ min: 1, max: 5 });

            for (let i = 0; i < RANDOM_LENGTH; i++) {
                mockedData.push({
                    __typename: 'MockedData',
                    id: faker.random.number(),
                    name: faker.commerce.productName(),
                    description: faker.commerce.productDescription()
                })
            }
            return mockedData;
        }
    },
    Mutation: {
        // here is the place for mocked mutations
    }
};

We made a function that returns mocked data. We created data using the Faker.js library. We return arrays with objects that have three fields: ID, name, description. As you can see, these are exactly the same fields we declared earlier in the GraphQL query.

Mock mutation

export const mockedDataResolvers = {
    Query: {
        ...
    },
    Mutation: {
        addMockedItem: (
            {
                name,
                description
            }
        ) => {
            return {
                id: faker.random.number(),
                name,
                description
            };
        }
    }
};

Above, we have declared the addMockedItem mutation, and here is its implementation. The mutation takes two parameters (name and description) and returns them here for the test. Additionally, we return the ID, which is a random number.

Our file is completed, and here is the final version:

import gql from "graphql-tag";
import faker from "faker/locale/en";

const GET_MOCKED_DATA = gql`
    query getMockedData {
        mocked_data @client {
            id
            name
            description
        }
    }
`;

const ADD_MOCKED_ITEM_MUTATION = gql`
    mutation addMockedItem(
        $name: String,
        $description: String
    ) {
        addMockedItem(
            name: $name,
            description: $description
        ) @client
    }
`;

export default {
    mutations: {
        addMockedItem: ADD_MOCKED_ITEM_MUTATION
    },
    queries: {
        getMockedData: GET_MOCKED_DATA
    }
};

export const mockedDataResolvers = {
    Query: {
        mocked_data: () => {
            let mockedData = [];
            const RANDOM_LENGTH = faker.random.number({ min: 1, max: 5 });

            for (let i = 0; i < RANDOM_LENGTH; i++) {
                mockedData.push({
                    __typename: 'MockedData',
                    id: faker.random.number(),
                    name: faker.commerce.productName(),
                    description: faker.commerce.productDescription()
                })
            }
            return mockedData;
        }
    },
    Mutation: {
        addMockedItem: (
            {
                name,
                description
            }
        ) => {
            return {
                id: faker.random.number(),
                name,
                description
            };
        }
    }
};

Register resolver in PWA Studio

For our resolver to work, we need to add it to the PWA Studio resolvers configuration.  This is found in the file:

@magento/venia-ui/lib/resolvers/resolvers.js

I recommend that you overwrite this file in a separate module using the Extensibility Framework.

import { giftOptionsResolvers } from '@magento/venia-ui/lib/components/CartPage/PriceAdjustments/GiftOptions/giftOptions.gql';
import { paymentInformationResolvers } from '@magento/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentInformation.gql';
import { mockedDataResolvers } from '@marcinkwiatkowski/mock-component-plugin/lib/queries/mocked-operations.gql';
/**
 * Type resolvers are merged by the client so spread each resolver into a
 * separate object.
 *
 * NOTE: Be careful not to overwrite type properties. For example, suppose two
 * resolvers are spread into the array resulting in the following. "foo" will be
 * overwritten while "bar" and "baz" will not be.
 *
 * [
 *   { // From Component A resolvers
 *     Query: {
 *       foo: () => { return 1; }
 *       bar: () => { return 2; }
 *     }
 *   },
 *   { // From Component B resolvers
 *     Query: {
 *       foo: () => { return 3; }
 *       baz: () => { return 4; }
 *     }
 *   }
 * ]
 */

export default [
    { ...paymentInformationResolvers },
    { ...giftOptionsResolvers },
    { ...mockedDataResolvers }
];

On line 3, you can see that we import our resolver, and on line 31, we export it along with the others.

Use our query and mutation in the app

Time to see our fake friends in action. We will make three components:

  1. MockComponent – displaying data
  2. MockedItem – showing single mocked element
  3. AddMock – form for adding a new item

MockComponent

Create the lib/components/MockedComponent directory.

File mock-component.js

import React from 'react';
import { mergeClasses } from '@magento/venia-ui/lib/classify';
import defaultClasses from './mock-component.css';
import { useQuery } from '@apollo/react-hooks';
import mockedOperations from '@marcinkwiatkowski/mock-component-plugin/lib/queries/mocked-operations.gql';
import AddMockItem from "@marcinkwiatkowski/mock-component-plugin/lib/components/AddMock/add-mock";
import MockedItem from "@marcinkwiatkowski/mock-component-plugin/lib/components/MockedItem";

const MockComponent = props => {
    const classes = mergeClasses(defaultClasses);
    const { queries } = mockedOperations;
    const { getMockedData } = queries;
    const { data } = useQuery(getMockedData, {
        fetchPolicy: 'network'
    });

    return (
        <div className={classes.root}>
            <h3 className={classes.primaryHeading}>Mocked data:</h3>
            {data &amp;&amp; data.mocked_data.map((data, i) => {
                return <MockedItem key={i} name={data.name} description={data.description}/>
            })}

            <AddMockItem/>
        </div>
    )
};

export default MockComponent;

The most important lines here are lines 11 through 15, where the getMockedData query is

File mock-component.css

// Styles for our component
:root {
    --mock-component-primary-heading-font-size: 48px;
    --mock-component-secondary-heading-font-size: 24px;
    --mock-component-border: grey;
}

.root {
    margin: 15px;
}

.primaryHeading {
    font-size: var(--mock-component-primary-heading-font-size);
    font-weight: bold;
}

.secondaryHeading {
    font-size: var(--mock-component-secondary-heading-font-size);
    font-weight: bold;
    margin-bottom: 10px;
}

Plik index.js

export { default } from './mock-component';

MockedItem component

Create the lib/components/MockedItem file.

File mocked-item.js

import React from "react";
import {mergeClasses} from '@magento/venia-ui/lib/classify';
import defaultClasses from './mocked-item.css';

const MockedItem = props => {
    const classes = mergeClasses(defaultClasses);
    const {name, description} = props;

    return (
        <div className={classes.mockedItem}>
            <h4 className={classes.secondaryHeading}>{name}</h4>
            <p>{description}</p>
        </div>
    )
};

export default MockedItem;

File mocked-item.css

.mockedItem {
    border-bottom: 1px solid var(--mock-component-border);
    padding: 15px 0;
}

.secondaryHeading {
    composes: secondaryHeading from '../MockComponent/mock-component.css';
}

File index.js

export { default } from './mocked-item';

AddMock component

Time for a component that will use our mocked mutation. We create the lib / components / AddMock directory.

add-mock.js file

import {mergeClasses} from '@magento/venia-ui/lib/classify';
import defaultClasses from './add-mock.css';
import mockedOperations from './../../queries/mocked-operations.gql';
import React from 'react';
import { useMutation } from '@apollo/react-hooks';
import { Form } from 'informed';
import TextInput from "@magento/venia-ui/lib/components/TextInput";
import {isRequired} from "@magento/venia-ui/lib/util/formValidators";
import Field from "@magento/venia-ui/lib/components/Field";
import Button from "@magento/venia-ui/lib/components/Button";
import TextArea from "@magento/venia-ui/lib/components/TextArea";

const AddMockItem = props => {
    const classes = mergeClasses(defaultClasses);
    const { mutations } = mockedOperations;
    const { addMockedItem } = mutations;
    const [addMockedItemMutation, { data }] = useMutation(addMockedItem);

    return (
        <div className={classes.root}>
            <h3 className={classes.primaryHeading}>Add new item using mocked mutation:</h3>
            <Form
                className={classes.form}
                onSubmit={formValues => {
                    const { name, description } = formValues;
                    addMockedItemMutation({
                        variables: {
                            name,
                            description
                        }
                    });
                }}
            >
                <Field id="name" label="Mock item name">
                    <TextInput
                        field="name"
                        validate={isRequired}
                    />
                </Field>
                <Field id="description" label="Mock item description">
                    <TextArea
                        field="description"
                        validate={isRequired}
                    />
                </Field>

                <div className={classes.buttons}>
                    <Button type="submit">Add Todo</Button>
                </div>


                {data &amp;&amp; data.addMockedItem &amp;&amp;
                    <div className={classes.result}>

                        <h4 className={classes.secondaryHeading}>Added item:</h4>
                        <h4><strong>Name: </strong>{data.addMockedItem.name}</h4>
                        <p><strong>description: </strong>{data.addMockedItem.description}</p>
                    </div>
                }
            </Form>
        </div>
    );
}

export default AddMockItem;

See line 17. There we use the useMutation hook, which takes our mocked mutation as a parameter. This hook returns two things to us:

  1. addMockedItemMutation – this is our mutation, which we later use on line 26.
  2. data – this is where data that our mutation will return after its execution will be available.

Note that the useMutation hook does not execute mutations. We do it later, and the data in the data object will be available only after the mutation is performed. We display this data, and the code on lines 52 to 59 is responsible for it.

Inject MockComponent to the storefront

The last thing we need to do to make our components visible on the frontend is to inject the MockComponent component into PWA Studio. For testing, I injected it into the <Main> component. Below is the result of our work:

PWA Studio GraphQL mocked data

Source files

If you would like to see how it works, you can use ready-made modules that I have prepared.

Pre-requirements

  1. NodeJS >=10.14.1 LTS
  2. Yarn >=1.13.0
  1. Yarn >=1.13.0

If you do not have PWA Studio installed locally, you can do it using these commands:

$ yarn create @magento/pwa
$ cd <folder w którym zainstalowałeś PWA Studio>
$ yarn buildpack create-custom-origin ./

Now add my packages: @marcinkwiatkowski/mock-component-plugin and @marcinkwiatkowski/mock-component-plugin-venia-overwrites

$ yarn add @marcinkwiatkowski/mock-component-plugin
$ yarn add @marcinkwiatkowski/mock-component-plugin-venia-overwrites

Run PWA Studio

$ yarn run watch

Below, I also provide links to the repositories of these two modules on my Github:

Summary

In this article, I showed you how you can 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.

Sources

https://en.wikipedia.org/wiki/Rubber_duck_debugging

https://github.com/apollographql/graphql-tag

https://github.com/marak/Faker.js/

https://scotch.io/tutorials/generate-fake-data-for-your-javascript-applications-using-faker

https://www.apollographql.com/docs/react/development-testing/client-schema-mocking/

https://github.com/magento/pwa-studio/pull/2620/files

WRITTEN BY

Marcin Kwiatkowski

Certified Magento Full Stack Developer who is passionate about Magento. He has 7 years of professional Software engineering experience.  Privately, husband, father, and DIY enthusiast.

 

You may also like

what is the difference beetween pwa studio and the current magento frontend
how to extend PWA Studio with new features?