npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@wayfair/gqmock

v1.3.1

Published

GQMock - GraphQL Mocking Service

Downloads

144

Readme

@wayfair/gqmock: GQMock - GraphQL Mocking Service

Release Lint codecov Contributor Covenant Maintainer

Table of Contents

About The Project

The problem

There isn't an easy way to customize mock data coming from Apollo Server without writing logic directly into the mock service implementation. This requires understanding the mock server templating implementation and hand rolling maintenance of that implementation directly.

As these custom mocking implementations grow, logic inside of mock servers becomes complex and counter-productive. Writing automated tests for both Frontends and Backends becomes more difficult and coupled in undesired ways.

The solution

@wayfair/gqmock offers an easy way to seed the data returned by GraphQL operations. It masks the complexities of managing a mock server implementation and instead exposes a declarative API for expressing the deterministic data you need in your tests. There is no additional overhead for adding more tests to your test suite, and because each test has a unique context, running tests in parallel is 💯 supported!

@wayfair/gqmock is an HTTP server which means that you can use it also outside of test environment for fast feature development. On top of that, if you cannot use the Node.js API that ships with this module, you can easily have the mock server running in a container and call the endpoints documented below to interact with the server.

Getting Started

To get a local copy up and running follow these simple steps.

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:

npm install --save-dev @wayfair/gqmock

or

for installation via yarn

yarn add --dev @wayfair/gqmock

Library API

GraphqlMockingService

| Parameter Name | Required | Description | Type | Default | | -------------- | -------- | -------------------------------- | ------- | ------- | | port | No | Port used to run the mock server | number | 5000 | | subgraph | No | Enable subgraph schema support | boolean | false |

async GraphqlMockingService.start

Starts the mocking server.

async GraphqlMockingService.stop

Stops the mocking server.

async GraphqlMockingService.registerSchema

Registers a schema with the mock server.

| Parameter Name | Required | Description | Type | Default | | --------------------- | -------- | ----------------------------------------------------- | ------- | ------- | | schema | Yes | A valid GraphQL schema | string | | | options | No | Schema registration options | object | | | options.fakerConfig | No | Map of fields to return realistic data using faker.js | object | {} | | options.subgraph | No | Is the schema a subgraph schema | boolean | false |

GraphqlMockingService.createContext

Creates a new GraphqlMockingContext instance with a unique sequenceId

| Parameter Name | Required | Description | Type | Default | | -------------- | -------- | ----------------------------------- | ------ | ------- | | sequenceId | No | A string to be used as a sequenceId | string | uuid |

GraphqlMockingContext

GraphqlMockingContext.sequenceId

A unique string used to match GraphQL requests with registered seeds. sequenceId can be attached to requests using a custom Apollo Link:

import {ApolloLink} from '@apollo/client';

const mockServiceLink = new ApolloLink((operation, forward) => {
  operation.setContext(({headers = {}}) => ({
    headers: {
      ...headers,
      ...(sequenceId ? {'mocking-sequence-id': sequenceId} : {}),
    },
  }));

  return forward(operation);
});

async GraphqlMockingContext.operation

Registers a seed for a GraphQL operation.

| Parameter Name | Required | Description | Type | Default | | ------------------------- | -------- | -------------------------------------------------------------------------------------- | -------- | -------------------------- | | operationName | Yes | Name of the GraphQL operation | string | | | seedResponse | Yes | See specific properties | object | | | seedResponse.data | No | Data to be merged with the default apollo server mock | object | {} | | seedResponse.errors | No | Errors to return | object[] | | | operationMatchArguments | No | Params used for matching a seed with GraphQL operations. By default matching is exact. | object | {} | | options | No | See specific properties | object | {} | | options.usesLeft | No | Uses left before discarding the seed | number | seed doesn't get discarded | | options.partialArgs | No | Allow partial matching of query arguments with the seed arguments | boolean | false | | options.statusCode | No | HTTP response status code of the response | number | 200 |

async GraphqlMockingContext.networkError

Registers a seed for a network error.

| Parameter Name | Required | Description | Type | Default | | ------------------------- | -------- | -------------------------------------------------------------------------------------- | ------------------------ | -------------------------- | | operationName | Yes | Name of the GraphQL operation | string | | | seedResponse | Yes | Error that will be sent from /graphql endpoint | object or string or null | | | operationMatchArguments | No | Params used for matching a seed with GraphQL operations. By default matching is exact. | object | {} | | options | No | See specific properties | object | {} | | options.usesLeft | No | Uses left before discarding the seed | number | seed doesn't get discarded | | options.partialArgs | No | Allow partial matching of query arguments with the seed arguments | boolean | false | | options.statusCode | No | HTTP response status code of the response | number | 500 |

Mock server endpoints

GET http:localhost:<port>/graphql/:operationName?

This endpoint supports serving a GraphQL IDE from the mock servers /graphql route. Three options are available:

  • DEFAULT GraphQLIDE.ApolloSandbox: Serve's the Apollo Sandbox experience.
  • GraphQLIDE.GraphiQL: Serve's the latest version of GraphiQL
  • GraphQLIDE.None: Disables the GraphQL IDE experience, and therefore this endpoint

POST http:localhost:<port>/graphql/:operationName?

Send GraphQL queries to this endpoint to retrieve mocked data. Seeds are overlaid onto the response if they were previously registered. The mocking-sequence-id needs to be sent with every request. This can be done automatically by configuring a custom Apollo Link for an Apollo Client. In order to use the registered seeds, the mocking-sequence-id header needs to match the sequeneceId used when the seed was registered.

| Parameter Name | Required | Description | Type | Default | | ----------------------------- | -------- | ------------------------------------------------------------------- | ------ | ------- | | body.operationName* | Yes | Name of the GraphQL operation | string | | | body.query | Yes | GraphQL query | string | | | body.variables | No | GraphQL query variables | object | {} | | headers.mocking-sequence-id | No | Unique id of the use case context used to connect or separate seeds | string | |

*: body.operationName is not required if the operationName is provided in the path.

POST http:localhost:<port>/schema/register

Schema needs to be registered first before mocked data can be retrieved.

| Parameter Name | Required | Description | Type | Default | | ----------------------------- | -------- | ------------------------------------------------------------------- | ------- | ------- | | body.schema | Yes | GraphQL SDL schema | string | | | body.options | No | See specific options | object | {} | | body.options.fakerConfig | No | Faker.js config for GraphQL type fields | object | {} | | body.options.subgraph | No | Is the schema a subgraph schema | boolean | false | | headers.mocking-sequence-id | Yes | Unique id of the use case context used to connect or separate seeds | string |

POST http:localhost:<port>/seed/operation

Use this endpoint to register operation seeds. You can register multiple seeds for the same operation. If there are multiple matched seeds then the one registered first will be used.

| Parameter Name | Required | Description | Type | Default | | ------------------------------ | -------- | -------------------------------------------------------------------------------------- | -------- | -------------------------- | | body.sequenceId | Yes | Unique id of the use case context used to connect or separate seeds | string | | | body.operationName | Yes | Name of the GraphQL operation | string | | | body.seedResponse | Yes | See specific properties | object | | | body.seedResponse.data | No | Data to be merged with the default apollo server mock | object | {} | | body.seedResponse.errors | No | Errors to return | object[] | | | body.operationMatchArguments | No | Params used for matching a seed with GraphQL operations. By default matching is exact. | object | {} | | body.options.usesLeft | No | Uses left before discarding the seed | number | seed doesn't get discarded | | body.options.partialArgs | No | Allow partial matching of query arguments with the seed arguments | boolean | false | | body.options.statusCode | No | HTTP response status code of the response | number | 200 |

POST http:localhost:<port>/seed/network-error

Use this endpoint to register network errors caused by executing GraphQL queries. For example, you can simulate unauthorized access.

| Parameter Name | Required | Description | Type | Default | | ------------------------------ | -------- | -------------------------------------------------------------------------------------- | ------------------------ | -------------------------- | | body.sequenceId | Yes | Unique id of the use case context used to connect or separate seeds | string | | | body.operationName | Yes | Name of the GraphQL operation | string | | | body.seedResponse | Yes | Error that will be sent from /graphql endpoint | object or string or null | | | body.operationMatchArguments | No | Params used for matching a seed with GraphQL operations. By default matching is exact. | object | {} | | body.options | No | See specific properties | object | {} | | body.options.usesLeft | No | Uses left before discarding the seed | number | seed doesn't get discarded | | body.options.partialArgs | No | Allow partial matching of query arguments with the seed arguments | boolean | false | | body.options.statusCode | No | HTTP response status code of the response | number | 500 |

Usage

Unit testing

describe('App', function () {
  let mockingService;
  beforeAll(async () => {
    mockingService = new GraphqlMockingService({port: 5000});
    await mockingService.start();

    const schema = fs.readFileSync(
      path.resolve(__dirname, './schema.graphql'),
      'utf-8'
    );
    await mockingService.registerSchema(schema);
  });

  afterAll(async () => {
    await mockingService.stop();
  });

  it('should work', async () => {
    const seed = {
      data: {
        booksByGenreCursorConnection: {
          edges: [
            {},
            {},
            {
              node: {
                id: 'Qm9vazo1',
                title: 'Harry Potter and the Chamber of Secrets',
                author: {
                  id: 'QXV0aG9yOjE=',
                  fullName: 'J. K. Rowling',
                  __typename: 'Author',
                },
                __typename: 'Book',
              },
              __typename: 'BooksEdge',
            },
          ],
        },
      },
    };

    const mockingContext = mockingService.createContext();
    await mockingContext.operation('GetBooks', seed, {genre: 'ALL'});

    render(<App sequenceId={mockingContext.sequenceId} />);
    const books = await screen.findAllByText(
      'Harry Potter and the Chamber of Secrets'
    );
    expect(books.length).toEqual(3);
  });
});

Chaining multiple seeds

const context = service.createContext();
await context.operation(/* ... */).operation(/* ... */).networkError(/* ... */);

Define operation seed response data

data is one of the allowed properties for seedResponse registration parameter. It is supposed to mimic the query response defined by the registered schema. As such, data will be a composition of objects and arrays all the way to primitive leaf fields. You can define objects in the following way:

const seedResponse = {
  data: {
    productBySku: {
      name: 'Flagship Table with Sku',
    },
  },
};

Defining lists in response data

List long-hand notation
const seedResponse = {
  data: {
    productBySku: {
      name: 'Flagship Table with Sku',
      variants: [
        {},
        {
          name: 'standing',
          tags: [{}, {value: 'adjustable'}],
        },
        {},
        {
          name: 'office',
          tags: [{}, {value: 'adjustable'}],
        },
      ],
    },
  },
};

In this example, variants is a list of 4 elements, and tags is a list of 2 elements. Only variants 2 and 4 will have seeded values.

List short-hand notation

The same lists can be defined using $length to define how many elements a list should have. $<index> is used to override selected items in the list. The prefix for both operations can be defined when the mocking service is initialized.

const seedResponse = {
  data: {
    productBySku: {
      name: 'Flagship Table with Sku',
      variants: {
        name: 'office',
        tags: {value: 'adjustable', $length: 2},
        $length: 4,
        $2: {name: 'standing', tags: {value: 'adjustable', $length: 2}},
      },
    },
  },
};

Faker.js support

const schema = `
    type ProductVariant {
        name: String
        color: String
        tags: [Tag]
        pictures: [Picture]
    }

    type Dimensions {
        length: Int
        width: Int
        height: Int
    }

    type Product {
        name: String
        variants: [ProductVariant]
        dimensions: Dimensions
    }`;

const fakerConfig = {
  Product: {
    name: {
      method: 'random.alpha',
      args: {count: 5, casing: 'upper', bannedChars: ['A']},
    },
  },
  Dimensions: {
    length: {
      method: 'random.numeric',
      args: 2,
    },
    width: {
      method: 'random.numeric',
      args: [2],
    },
    height: {
      method: 'random.numeric',
      args: 3,
    },
  },
  ProductVariant: {
    name: {
      method: 'random.words',
    },
  },
};

const mockingService = new GraphqlMockingService();
await mockingService.start();
await mockingService.registerSchema(schema, {fakerConfig});
query getProduct {
  product {
    name
  }
}

will resolve as:

{
  "data": {
    "product": {
      "name": "DTCIC"
    }
  }
}

Setup outside of testing environment

Required client setup

  • Create a custom link to attach sequenceId as a header if it is present.
  • Configure it into your ApolloClient's link chain
import {ApolloLink, HttpLink, concat} from '@apollo/client';
import fetch from 'cross-fetch';

const setCustomHeaders = new ApolloLink((operation, forward) => {
  operation.setContext(({headers = {}}) => ({
    headers: {
      ...headers,
      ...(sequenceId ? {'mocking-sequence-id': sequenceId} : {}),
    },
  }));

  return forward(operation);
});

const httpLink = new HttpLink();

const client = new ApolloClient({
  // other configuration here
  link: concat(setCustomHeaders, httpLink),
});

Required server setup

Start the mocking server and register a schema

import GraphqlMockingService from '@wayfair/gqmock';

const mockingService = new GraphqlMockingService({port: 5000});
await mockingService.start();
await mockingService.registerSchema(schema, options); // or register schema by calling the endpoint documented above

That's it. You can now register seeds and call /graphql endpoint to get seeded data.

Docker support

GQMock was written with Node.js in mind. However, the mocking server can be dockerized and used in any environment. A docker image can be built at any time by running

docker build . -t wayfair-incubator/gqmock

Then you can run the container

docker run -dp <port on local port>:5000 wayfair-incubator/gqmock

The running server accepts requests to all documented endpoints on the port specified when starting the container.

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. For detailed contributing guidelines, please see CONTRIBUTING.md

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Project Link: https://github.com/wayfair-incubator/gqmock

Acknowledgements

This template was adapted from https://github.com/othneildrew/Best-README-Template.