/graphql-query-generator

Randomly generates GraphQL queries from a GraphQL schema

Primary LanguageTypeScriptMIT LicenseMIT

GitHub last commit npm

GraphQL Query Generator

This library will generate randomized GraphQL queries from a given schema.

It can be used in a few ways:

  • Engineering: If you operate a GraphQL service, you might use this library to:
    • develop a static test suite of GraphQL queries
    • develop a battery of queries to test the effect of performance improvements
  • Scientific: Understand the characteristics of various GraphQL services

Usage

Install the library using:

npm i ibm-graphql-query-generator

Usage typically relies on the generateRandomQuery function, which can be imported like this:

const { generateRandomQuery } = require("ibm-graphql-query-generator")

Minimal working example

All arguments are exposed as variables. Providers can be passed to provide values for these variables. For example:

const { generateRandomQuery } = require("ibm-graphql-query-generator")

const { buildSchema, print } = require("graphql")

const schema = `
  type Query {
    getUser(name: String!): User
    getCompany(companyName: String!): Company
  }

  type User {
    firstName: String
    lastName: String
    employer: Company
    friends: [User]
  }

  type Company {
    name: String
    CEO: User
    employees: [User]
  }
`

const configuration = {
  depthProbability: 0.5,
  breadthProbability: 0.5,
  providerMap: {
    "*__*__name": () => {
      const nameList = ["Alfred", "Barbara", "Charles", "Dorothy"]

      return nameList[Math.floor(Math.random() * nameList.length)]
    },
    "*__*__companyName": () => {
      const companyNameList = [
        "All Systems Go",
        "Business Brothers",
        "Corporate Comglomerate Company",
        "Data Defenders"
      ]

      return companyNameList[
        Math.floor(Math.random() * companyNameList.length)
      ]
    }
  }
}

const { queryDocument, variableValues, seed } = generateRandomQuery(
  buildSchema(schema),
  configuration
)

console.log(print(queryDocument))
console.log(variableValues)

Example configurations

We provide sample query generators for the following APIs:

Generating random queries

This library exposes two functions for generating random GraphQL queries:

  • getRandomQuery(schema: GraphQLSchema, config: Configuration): Produces a random query from the given schema, and considering the passed configuration.
  • getRandomMutation(schema: GraphQLSchema, config: Configuration): Produces a random mutation from the given schema, and considering the passed configuration.

Configuration

Functions of this library accept a configuration object with the following properties:

  • depthProbability (type: number, default: 0.5): The probability (from 0 to 1) that, if existent, fields that themselves have subfields are selected at every level of the generated query. The number of so selected fields depends on the breadthProbability.
  • breadthProbability (type: number, default: 0.5): The probability (from 0 to 1) that a field (nested or flat) is selected at every level of the generated query.
  • maxDepth (type: number, default: 5): The maximum depths of the query / mutation to generate. This library ensures that leave nodes do not require children fields to be selected.
  • ignoreOptionalArguments (type: boolean, default: true): If set to true, non-mandatory arguments will not be included in the generated query / mutation.
  • argumentsToIgnore (type: string[], default: []): List of argument names that should be ignored in any case. If non-null arguments are configured to be ignored, an error will be thrown.
  • argumentsToConsider (type: string[], default: []): List of argument names that should be considered, even if the argument is optional and ignoreOptionalArguments is set to true.
  • providerMap (type: {[varNameQuery: string]: any | ProviderFunction }, default: {}): Map of values or functions to provide values for the variables present in the generated query / mutation. See details below.
  • providePlaceholders (type: boolean, default: false): If enabled, instead of defaulting to null, placeholder values of correct type are provided for variables. Specifically, the placeholders are 10 for type Int, 10.0 for type Float, true for type Boolean, and "PLACEHOLDER" for types String and ID, and custom scalars.
  • considerInterfaces (type: boolean, default: false): Create queries containing interfaces (by calling fields in the interfaces and/or creating fragments on objects implementing the interfaces)
  • considerUnions (type: boolean, default: false): Create queries containing unions (by creating fragments on objects of the unions)
  • seed (type: number, optional): Allows the generator to produce queries deterministically based on a random number generator seed. If no seed is provided, a random seed will be provided. The seed that is used to produce the query, whether user-provided or randomly generated, will be included in the output.
  • pickNestedQueryField (type: boolean, default: false): Guarantees that the generator will pick at least one nested field.

Example:

const cfg = {
  'depthProbability':        0.5,
  'breadthProbability':      0.5,
  'maxDepth':                5,
  'ignoreOptionalArguments': true,
  'argumentsToIgnore':       [],
  'argumentsToConsider':     [],
  'providerMap':             {'*__*__*': null},
  'considerInterfaces':      false,
  'considerUnions':          false,
  'seed':                    1,
  'pickNestedQueryField':    false
}

Provider map

Whenever a randomly generated query or mutation requires an argument, this library exposes that argument as a variable. The names of these variables reflect the type and field that the argument applies to, as well as the argument name like so:

<type>__<fieldName>__<argumentName>

Alternatively, you can match using:

<type>__<fieldName>

In this case, the provider function returns an object where multiple arguments are present.

The providerMap contains values or value producing functions for the variables in a query.

The keys of the providerMap are either the exact name of the variable or a wildcard where either the type, fieldName, and/or argumentName are replaced by a *. For example, the key *__*__limit matches all variables for arguments of name limit, no matter for what field the argument is used or in which type. If no providerMap is passed, a default map {'*__*__*': null} is used, which provides a null value to all variables (Note: this can be problematic if an argument defines a non-null value).

The values of the providerMap are either the concrete argument values, or a function that will be invoked to provide that value. A provider function will get passed a map of all already provided variable values, which allows to provide values based on previous ones. It will also get passed the argument type (as a GraphQLNamedType) as a second argument.

This library also exposes a function matchVarName(query: string, candidates: string[]): string that, from a given list of variable names and/or variable name queries, finds the one matching the given variable name or query.

Note that for variables with an enumeration type, this library automatically chooses one value at random.

Errors

Generating random queries or mutations may fail in some cases:

  • An error is thrown if a query hits the defined maxDepth, but there are only fields with children to choose from. Choosing such a field but then not choosing a sub-field for it (due to the maxDepth constraint) would result in an invalid query and thus causes this library to throw an error.
  • An error is thrown if there is no provider defined for a variable in the generated query.

Citing this library

If you use this library in a scientific publication, please cite:

  1. The library, as: IBM, graphql-query-generator, 2020. https://github.com/IBM/graphql-query-generator.
  2. The work in which we introduced it, as: Cha, Wittern, Baudart, Davis, Mandel, and Laredo. A Principled Approach to GraphQL Query Cost Analysis. ESEC/FSE 2020.