# Mock an existing service

This tutorial will gide you through the steps to create a mock for an existing service.

For this example, we'll mock the service projects-api.datacamp.com.

# Create a Poser server

If you don't have a poser server alreayd, let's create one:

yarn create @datacamp/poser-server

The code generator will ask you for a name, we'll choose mock-server. This will create an empty poser server in a folder named mock-server.

Next, let's go into that folder and install dependencies

cd mock-server
yarn install

We now have a server to run our service 🎉

# Create a service mock

To create a service, we'll also use a generator. From the root of our mock server, we run the generator:

yarn create @datacamp/poser-service

The code generator will ask you for a name, well' choose projects-api. It will then ask where to put this service, we'll keep the default services/.

The code generator created a few files in services/projects-api, let's import and register it:

index.ts
import projectsApi from './services/projects-api';
poser.registerService('projects-api', projectsApi);

Let's make sure everything is working to this point by running the server:

yarn start

Visiting http://localhost:1000 should show projects-api under "Registered services":

Poser frontpage shows the service is registered
Poser frontpage shows the service is registered

The generator includes a dummy schema you can interact with by visiting http://localhost:1000/projects-api/graphql. Let's now import the schema from the actual projects-api service.

# Fetch and use existing schema

The first step is to locate the graphQL endpoint. That endpoint must have introspection enabled.

The project's README lists the following endpoint: https://projects-api.datacamp-staging.com/api/internal-graphql/query, that's the one we'll use.

It also says we'll need a "Basic auth" token. Each service may have different means of obtainig credentials, so we won't describe it in details here. We'll simply follow the steps in the README.

Now that we have a url and credentials, we can use Poser's command fetch-schema to retrieve it:

yarn poser fetch-schema --url https://projects-api.datacamp-staging.com/api/internal-graphql/query --output ./services/projects-api/graphql/schema.graphql --headers '{"authorization": "Basic xxxxx"}'

Let's unpack this command:

  • yarn poser we're executing the command poser through yarn
  • fetch-schema the name of the poser command
  • --url the url of the graphQL endpoint
  • --output where to write the schema locally
  • --headers since this endpoint requires basic auth, we provide an authorization header

We should now have an updated schema in services/projects-api/graphql/schema.graphql

Let's try our schema by visiting http://localhost:1000/projects-api/graphql

You should now be able to query this schema as if it was the original. GraphiQL (and any other graphQL client) will also display the documentation, as our endpoint poses as the real thing.

The full schema is auto mocked, let's try a query:

Our first query to the mock service
Our first query to the mock service

The mock api responds, but we'll need to tweak mocks and resolvers to get a more realistic response.

# Tweak mocks and resolvers

There are a few things wrong with the current response to our query:

  • We're fetching the project of id 1, and yet we're getting back a project with a different id.
  • technologyId is not realistic
  • timeNeeded is a random string, but the reality is more something like "2 hours"
  • tasks usually has more than 2, and task.number is not realistic (should be 1, 2, 3, ...)

# Fix the id

Let's fix the id. To do this, we'll need to specify a custom resolver for Query.project

graphql/resolvers.ts
export const resolvers: ResolverFn = (store) => {
  return {
    // aka "Query"
    ProjectsApiSchema: {
      project: (_, { id }) => {
        return store.get('Project', id);
      },
    },
  };
};

Let's try our query again, you should now get the same id you requested 🎉

# Fix technologyId

technologyId normally matches a technology id from main-app (that's the service that owns technology ids). For the sake of simplicity, let's make it so our projects are either "R" (id=1) or "Python" (id=2).

This time we'll simply need to provide a mock for the field Project.technologyId, this way anytime we try reading a technologyId from a Project, no matter from which query or mutation, we'll hit that mock.

graphql/mocks.ts
import { faker, Mocks } from '@datacamp/poser';

export const mocks: Mocks = {
  Project: {
    technologyId: () => faker.random.arrayElement([1, 2]),
  },
};

From now on, all Project.technologyId will either be 1 or 2.

# Fix timeNeeded

Fixing timeNeeded is very similar to fixing technologyId, we'll simply add a mock for Project.timeNeeded:

timeNeeded: () => `${faker.datatype.number({ min: 2, max: 7 })} hours`,

# Fix tasks

Finally, let's make some more realistic tasks. We'll return 10 of them and make sure the numbering matches:

For this we'll provide a custom Mock for Project.tasks

graphql/mocks.ts
export const mocks: Mocks = {
  Project: {
    // ... other fields omitted for breveity
    tasks: () => {
      return utils.mockList(10).map((_, i) => ({ number: i + 1 }));
    },
  },
};

That's it! We now have a pretty realistic response:

The mock service now returns more realistic values
The mock service now returns more realistic values

# Customizing further

Is this response really realistic ? The answer is: it depends.

Let's take the example of timeNeeded.

Originally, it's defined as a String. We might be mocking this for a simple frontend that will simply display that text, probably next to a cute "clock" icon. If the only thing our app does is displaying the string to a user, there is no real value in mocking it. Any string would do, functionally speaking.

Of course, your UI may look strange, but is it what you need to test ?

However, if your app is actually going to parse that string to extract the time, then you definitely need to mock it realistically, as a random string would break the parser.

It really depends on your requirements and preferences, but one rule must be kept in mind: never mock more than you need. Mocks and resolvers will require maintenance from time to time, keeping them lean will help in the long run.