graphql-mocks / graphql-mocks

Build web applications today against the GraphQL APIs of tomorrow.
http://www.graphql-mocks.com
MIT License
58 stars 7 forks source link

Create a mocked GraphQL type directly #284

Open bengry opened 1 week ago

bengry commented 1 week ago

As detailed in this gist (also shared in https://github.com/graphql-mocks/graphql-mocks/issues/273), we're using graphql-mocks to create fake data for GQL requests.

Sometimes though we want to create a semi-fake type. i.e. create a basic type ourselves (similar to what DeepPartial<SomeGQLType> would do in TypeScript), and let graphql-mocks fill in the rest of the stuff.

Ideally something like:

import type { User } from 'path-to-generated-gql-types';

const mockedUser = createMockOf<User>('User', {
  name: {
    first: 'John'
  }
});

assert.equal(mockedUser, {
  name: {
    first: 'John',
    last: 'Doe', // mocked automatically,
  },
  address: {
    ...
  }
});

Is such a thing possible? The entire mocking infrastructure already exists, but I'm not sure if there's a userland API for connecting the dots and creating this createMockOf function.

chadian commented 1 day ago

That would make for a nice helper. Is there more to the example where mockedUser is being returned from a resolver and part of a graphql query?

One thing that makes it difficult, and maybe this doesn't matter in your case, is that with resolver functions you aren't creating individual User objects. Since first and last are individual resolver functions, if first always returned "John".

The query:

{
  users {
    first
    last
  }
}

would return

{
  "data": [
    { "first": "John", "last": "Smith" },
    { "first": "John", "last": "Johnson" }, 
  ]
}

That would look something like this:

import { setResolver } from 'graphql-mocks/resolver-map';
const createUserMockMiddleware = (hardcodedFirstName) => (resolverMap) => {
  setResolver(resolverMap, ["User", "first"], () => hardcodedFirstName);
  setResolver(resolverMap, ["User", "last"], () => faker.name());
}

If you wanted something less hardcoded and more representative of instances of users then decoupling the User from the resolver function to something stateful is helpful. Using graphql-paper comes in handy. This technique is better described in GraphQL Paper: Separation of Concrete and Derived Data. In this case the concrete data is first and the derived (dynamic) is last.

import { Paper } from "graphql-paper";

const paper = new Paper(graphqlSchema);

const userMiddleware = (resolverMap, { dependencies }) => {
  setReference(resolverMap, ['User', 'first'], (parent, args, context, info) =>
    // return a specific user based on some criteria
    dependencies.paper.User.find((user) => user.id === args.id)?.name
  );

  setReference(resolverMap, ['User', 'last'], () => sinon.name());
};

const handler = new GraphQLHandler({
  dependencies: {
    graphqlSchema,
    paper,
  },
});

// if the `async`
beforeEach(async () => {
  paper.mutate(({ create }) => {
    // create as many specific Users as needed
    create("User", { first: "John" });
    create("User", { first: "Jessica" });
  });
});

The last thing I'll add is that the default resolver for a field is to look at its parent and return the property that matches the field name, so if you have a schema like:

schema {
  query: Query
}

type Query {
  user: User
  users: [User!]!
}

type User {
  first: String
  last: String
}
const createMockuser = (partial) => {
  return {
    last: sinon.name(),
    ...partial,
  };
};

const resolverMap = {
  Query: {
    user: () => createMockUser({ first: "John" })
    users: () => [createMockUser({ first: "Bob" }), createMockUser({ first: "Jessica" })]
  }
};

This might be a simple way of doing what you're after.

I think GraphQL Paper: Separation of Concrete and Derived Data is probably the closest fit? Let me know if I'm missing something or if this isn't quite what you're after. It might be helpful to see the example as the assertion of mockedUser involving the resolvers and/or query.