Mock your GraphQL server realistically with faker.js

August 07, 2019

Sometimes your GraphQL server need to use an API that isn't implemented yet or that is unavailable temporarly. Mocking seems the right thing to do in this case but it's hard to maintain good mock data so we end up with "Lorem ipsum" everywhere in our apps.

faker.js

faker.js is a library that let you generate fake data in node or in the browser. It embarks a lot of methods to generate data for common use cases like:

  • names
  • addresses
  • phones
  • images
  • companies
  • ...

Even for... Lorem ipsum!

Other tools

We will use Koa to create the server. It is "a new web framework designed by the team behind Express". In practice, it has a different middleware implementation and embarks less things than express. If you want more information a comparaison is available on Koa's repository.

For GraphQL implementation we will use Apollo Server. It seems to be the most popular server implementation of GraphQL and it fits well with Koa since an apollo-server-koa package exists and have been implemented by the Apollo Server team.

Finally we will use the well-known cross-env package to set environnement variables regardless of the platform you are using.

Setting up the server

First of all, we create a folder with the following structure:

.
├── package.json
└── src
  └── index.js

For this article we will be using yarn but you can use npm as well. Init package.json with the init command:

yarn init

Install the following dependencies:

yarn add apollo-server-koa cross-env faker graphql koa

Add a start script that execute our src/index.js file:

{
"scripts": {
"start": "node src/index.js"
}
}

In the src/index.js file we instantiate a new Koa application:

const Koa = require('koa');
const app = new Koa();
app.listen({port: 4000}, () =>
console.log(`🚀 Server ready at http://localhost:4000`),
);

Run your start script. You should see the console.log message present in the above example in the console output.

Create the GraphQL endpoint

It is time to implement our GraphQL endpoint. Let's say we are in the situation where an API isn't implemented yet. This API is supposed to expose a list of persons with their firstname and lastname. We will define a schema and make it accessible on a /graphql endpoint with apollo-server-koa.

Import ApolloServer and gql from apollo-server-koa:

const {ApolloServer, gql} = require('apollo-server-koa');

We define a query that returns a list of person and the type Person itself:

const typeDefs = gql`
type Person {
lastname: String
firstname: String
}
type Query {
persons: [Person]
}
`;

Since the API doesn't exist yet we throw an error to inform user that he can't use this query:

const resolvers = {
Query: {
persons: () => {
throw Error('Not yet implemented');
},
},
};

We instantiate an Apollo server with the our type definitions and the resolver for our query:

const server = new ApolloServer({
typeDefs,
resolvers,
});

Finally, apply the GraphQL middleware previously created to the Koa application:

server.applyMiddleware({app});

If you start your server right now with the command yarn start and open the url http://localhost:4000/graphql in your browser. You should see the nice interface of GraphQL IDE.

GraphQL IDE

If you type a query to retrieve the list of persons with their data:

{
persons {
firstname,
lastname
}
}

It should result in getting the following error:

{
"errors": [
{
"message": "Not yet implemented",
}
]
}

Mock it with faker.js

This error isn't the expected result. We want random realistic data when our server is mocked. To achieve it we need to override the default resolver that throw an error by another that generate fake data.

For this purpose we will set the NODE_ENV environnement variable to mock to determine which behavior our Apollo server should follow. We will achieve that by adding a mock script in our package.json that set the NODE_ENV variable using cross-env and calling the start script:

{
"scripts": {
"start": "node src/index.js",
"mock": "cross-env-shell NODE_ENV=mock yarn start"
}
}

Apollo server has a mocks property in its options that take either a Boolean or a object with mocked resolvers. To begin we will set it to true if NODE_ENV is equal to mock:

const server = new ApolloServer({
typeDefs,
resolvers,
mocks: process.env.NODE_ENV === 'mock' ? true : false,
});

At this step if you re-execute your query in the GraphQL IDE you end up with a first result:

{
"data": {
"persons": [
{
"firstname": "Hello World",
"lastname": "Hello World"
},
{
"firstname": "Hello World",
"lastname": "Hello World"
}
]
}
}

That's nice but it isn't really realistic. To add faker we need to implement our own mocked resolver and pass it to Apollo server. In mocked resolvers the Query property has to be a function that return an object with the resolvers definition. In our resolver for persons query we return an array of two person.

faker.js methods are organized in namespace. We will use the name namespace that contains methods such as:

  • firstName
  • lastName
  • jobTitle
  • title
  • ...

You guess it, we will use firstName and lastName methods to generate random data for our two persons:

const mockResolvers = {
Query: () => ({
persons: () => [
{
firstname: name.firstName(),
lastname: name.lastName(),
},
{
firstname: name.firstName(),
lastname: name.lastName(),
},
],
}),
};
// ...
const server = new ApolloServer({
typeDefs,
resolvers,
mocks: process.env.NODE_ENV === 'mock' ? mockResolvers : false,
});

Execute your query in the GraphQL IDE you now have nice datas like:

{
"data": {
"persons": [
{
"firstname": "Mélissa",
"lastname": "Mathieu"
},
{
"firstname": "Camille",
"lastname": "Richard"
}
]
}
}

Bonus

Here is a method I use to generate random sized arrays for my mocked resolvers:

const randomArray = (min, max, callback) => {
const size = random.number({min, max});
return Array.from({length: size}, callback);
};

We could refactor the previous resolver using this method like this:

const mockResolvers = {
Query: () => ({
persons: () =>
randomArray(2, 6, () => ({
firstname: name.firstName(),
lastname: name.lastName(),
})),
}),
};

I made a repository with all the sources presented in this article. Feedback is appreciated 🙏 Please tweet me if you have any questions @YvonnickFrin!


Profile picture

Lead developer at Pix.
I'm NantesJS co-organizer on my spare time.

Twitter . Github