# Mocking levels

When a request hits the mock server, it is crucial to understand how fields are resolved.

Example query request

graph LR
  A((Query)) --> B(Resolvers)
  B --> C(Store)
  C --> D(Mocks)
  1. Resolvers: First (and always) any resolvers are processed. If no resolver is defined for a given field, it will fallback to the default which returns a random value for the return type.
  2. Store: This holds all objects that have been returned by the mock server and acts as a cache. If the store already has a value for an instance field, that value will be returned. If the store doesn't have a value yet, it will use the mocks to generate one and cache it.
  3. Mocks: The store uses mocks to get values for fields. It will first try the specific field mock (such as User.username) then fallback to a Scalar type mock (such as String) if undefined.

To understand the different levels of mocking, consider an example schema:

type User {
  id: String!
  username: String!
}

type Query {
  userById(id: String!): User
} 

All fields are "auto-mocked" by default and each field will produce a value based on the field's type. In our example, the type User has two String fields which will both be mocked using a random string.

# Mocks

# Scalar types

You may redefine defaults for scalar types such as String and Integer. This is the final fallback and if no value is provided by the resolvers or "type field" mocks, the mock server will use the corresponding "scalar type" mock.

Querying for User.id or User.username will return a random string. This comes from the defaults provided by Poser.

import { faker } from '@faker-js/faker';

import type { Mocks } from '../types';

const defaultMocks: Mocks = {
  DateTime: () => faker.date.past().toISOString(),
  Int: () => faker.datatype.number(),
  JSON: () => faker.datatype.json(),
  String: () => faker.random.words(4),
};
export default defaultMocks;

We can specify our own default for any scalar type by providing mocks:

const mocks = {
  String: () => 'All your strings are belong to us'
}

With this configuration, all fields of type String will return "All your strings are belong to us".

# Type fields

We can also redefine what data should be returned for specific fields. In the above example, we have a type User with fields id and username which both fallback to String.

To redefine the User.id field:

const mocks = {
  User: {
    id: () => faker.datatype.string(7)
  }
}

With this configuration, User.id will always return a random string such as "fiy61g6".

Mocks
/graphql/mocks/

# Resolvers

Mocks are great for returning random values for local development which is closer to reality. However, mocks are limited and cannot implement logic. To go further and perform more complex mocking, use resolvers.

The first thing to be processed during a request are the resolvers. As we saw, mocks can only provide values to the store, but resolvers are fully in charge of what is returned. Resolvers may choose to query from the store or return entirely new values.

Resolvers look a lot like mocks, but behave very differently. Consider the same mock and resolver for User.name:

const mocks = {
  User: {
    name: () => faker.name.firstName()
  }
}

const resolvers = () => {
  return {
    User: {
      name: () => faker.name.firstName()
    }
  }
}

The implementation is identical, but the results are different:

  • when using a Mock, our User.name will be cached in the store and attached to user instances.
  • when using a Resolver, our User.name will be different every time we query it. This is because resolvers don't use the store, meaning the value is never cached.

Unlike mocks, resolver functions receive four arguments which are identical to an actual GraphQL server.

Resolvers
/graphql/resolvers/

# The store

The store can be seen as an object store (a bit like mongo or any NoSQL database), except it always has the object you're looking for. If it doesn't have the object you're looking for, it will create one (using mocks to populate fields), return it and store it for the next time you ask for it.

It's like an infinite random database! 🎲

Store documentation
/graphql/the-store/