GraphQL Requests

Learn how to send GraphQL queries and mutations, and check their responses.

Prerequisites

Gatling GraphQL SDK is not imported by default.

You have to manually add the following imports:

     
import static io.gatling.javaapi.graphql.GraphQlDsl.*;
import io.gatling.javaapi.graphql.GraphQlDsl.*
import io.gatling.graphql.Predef._

Define an operation

Use the graphql object to define an operation. The protocol must be registered.

Gatling parses the document when the Simulation is built. It fails fast if the document is invalid or can’t be found.

Inline documents

Use query and mutation to define an operation with an inline document. Gatling checks that the document declares the expected operation type.

     
exec(graphql.query("query GetUser($id: ID!) { user(id: $id) { name } }").variable("id", 42));
exec(graphql.mutation("mutation CreateOrder($input: OrderInput!) { createOrder(input: $input) { id } }")
  .variable("input", Map.of("sku", "abc")));
exec(graphql.query("query GetUser(\$id: ID!) { user(id: \$id) { name } }").variable("id", 42))
exec(graphql.mutation("mutation CreateOrder(\$input: OrderInput!) { createOrder(input: \$input) { id } }")
  .variable("input", mapOf("sku" to "abc")))
exec(graphql.query("query GetUser($id: ID!) { user(id: $id) { name } }").variable("id", 42))
exec(
  graphql
    .mutation("mutation CreateOrder($input: OrderInput!) { createOrder(input: $input) { id } }")
    .variable("input", Map("sku" -> "abc"))
)

Files

Use file to load a document from a classpath resource, typically a .graphql file. The operation type is whatever the document declares.

     
exec(graphql.file("graphql/getUser.graphql").variable("id", 42));
exec(graphql.file("graphql/getUser.graphql").variable("id", 42))
exec(graphql.file("graphql/getUser.graphql").variable("id", 42))

If the document declares several operations, use operationName to pick the one to execute.

     
exec(graphql.file("graphql/multiple.graphql").operationName("CreateOrder"));
exec(graphql.file("graphql/multiple.graphql").operationName("CreateOrder"))
exec(graphql.file("graphql/multiple.graphql").operationName("CreateOrder"))

Any document

Use document to define an operation with an inline document, whatever operation type it declares.

     
exec(graphql.document("{ me { id } }"));
exec(graphql.document("{ me { id } }"))
exec(graphql.document("{ me { id } }"))

Dynamic documents

Use dynamicDocument when the document is only known at runtime, for example to replay a corpus of captured operations from a feeder. As Gatling can’t parse the document when the Simulation is built, you must provide the request name. Dynamic documents don’t support operation type assertion, persisted queries, or overGet.

     
// request name and document as Expression Language Strings
exec(graphql.dynamicDocument("#{operationName}", "#{document}"));
// request name as a String, document as a function
exec(graphql.dynamicDocument("dynamic", session -> session.getString("document")));
// request name and document as Expression Language Strings
exec(graphql.dynamicDocument("#{operationName}", "#{document}"))
// request name as a String, document as a function
exec(graphql.dynamicDocument("dynamic") { session -> session.getString("document") })
// request name and document as Expression Language Strings
exec(graphql.dynamicDocument("#{operationName}", "#{document}"))
// request name as a String, document as a function
exec(graphql.dynamicDocument("dynamic", session => session("document").validate[String]))

Request name

Gatling reports a request under the name <operation type> <operation name>, for example query GetUser. See operation naming for how to name anonymous operations.

Use requestName to set the name explicitly.

     
exec(graphql.file("graphql/getUser.graphql").requestName("Get current user"));
exec(graphql.file("graphql/getUser.graphql").requestName(session -> "Get user " + session.getString("userId")));
exec(graphql.file("graphql/getUser.graphql").requestName("Get current user"))
exec(graphql.file("graphql/getUser.graphql").requestName { session -> "Get user " + session.getString("userId") })
exec(graphql.file("graphql/getUser.graphql").requestName("Get current user"))
exec(graphql.file("graphql/getUser.graphql").requestName("Get user #{userId}"))

Variables

Use variable to set a variable. The value can be a static value, a Gatling Expression Language String, or a function.

     
graphql.file("graphql/getUser.graphql")
  // static value
  .variable("id", 42)
  // Expression Language String
  .variable("locale", "#{locale}")
  // function
  .variable("token", session -> session.getString("token"));
graphql.file("graphql/getUser.graphql")
  // static value
  .variable("id", 42)
  // Expression Language String
  .variable("locale", "#{locale}")
  // function
  .variable("token") { session -> session.getString("token") }
graphql
  .file("graphql/getUser.graphql")
  // static value
  .variable("id", 42)
  // Expression Language String
  .variable("locale", "#{locale}")
  // function
  .variable("token", session => session("token").validate[String])

Use variables to set several variables at once, with a Map or a function that returns a Map. Top level String values of a Map can contain Gatling Expression Language placeholders.

     
graphql.file("graphql/getProducts.graphql")
  .variables(Map.of("first", 10, "after", "#{cursor}"));
graphql.file("graphql/getProducts.graphql")
  .variables(session -> Map.of("first", 10, "after", session.getString("cursor")));
graphql.file("graphql/getProducts.graphql")
  .variables(mapOf("first" to 10, "after" to "#{cursor}"))
graphql.file("graphql/getProducts.graphql")
  .variables { session -> mapOf("first" to 10, "after" to session.getString("cursor")) }
graphql
  .file("graphql/getProducts.graphql")
  .variables(Map("first" -> 10, "after" -> "#{cursor}"))
graphql
  .file("graphql/getProducts.graphql")
  .variables(session => session("variables").validate[Map[String, Any]])

Use variablesJson to set all the variables at once, as a JSON object that can contain Gatling Expression Language placeholders. The JSON is sent as is, so use jsonStringify() for a value that must be JSON escaped. variablesJson can’t be combined with variable or variables.

     
graphql.mutation("mutation CreateOrder($input: OrderInput!) { createOrder(input: $input) { id } }")
  .variablesJson("{\"input\": {\"items\": [{\"sku\": #{sku.jsonStringify()}, \"quantity\": #{randomInt(1, 4)}}]}}");
graphql.mutation("mutation CreateOrder(\$input: OrderInput!) { createOrder(input: \$input) { id } }")
  .variablesJson("""{"input": {"items": [{"sku": #{sku.jsonStringify()}, "quantity": #{randomInt(1, 4)}}]}}""")
graphql
  .mutation("mutation CreateOrder($input: OrderInput!) { createOrder(input: $input) { id } }")
  .variablesJson("""{"input": {"items": [{"sku": #{sku.jsonStringify()}, "quantity": #{randomInt(1, 4)}}]}}""")

HTTP request options

A GraphQL request supports the following HTTP options:

     
graphql.file("graphql/getUser.graphql")
  .endpoint("/other/graphql")
  .header("X-Trace", "#{traceId}")
  .headers(Map.of("X-Client", "gatling"))
  .requestTimeout(Duration.ofSeconds(10))
  .silent()
  .ignoreProtocolChecks();
graphql.file("graphql/getUser.graphql")
  .endpoint("/other/graphql")
  .header("X-Trace", "#{traceId}")
  .headers(mapOf("X-Client" to "gatling"))
  .requestTimeout(Duration.ofSeconds(10))
  .silent()
  .ignoreProtocolChecks()
graphql
  .file("graphql/getUser.graphql")
  .endpoint("/other/graphql")
  .header("X-Trace", "#{traceId}")
  .headers(Map("X-Client" -> "gatling"))
  .requestTimeout(10.seconds)
  .silent
  .ignoreProtocolChecks
  • endpoint: override the protocol endpoint for this request only.
  • header and headers: set headers.
  • requestTimeout: override the request timeout.
  • silent and notSilent: control whether the request is reported in the statistics.
  • ignoreProtocolChecks: ignore the checks defined on the HTTP protocol.

Send a query over GET

Use overGet to send a query with a GET request, with the query, operationName and variables as query string parameters. This makes responses cacheable by a CDN. Unlike the protocol queriesOverGet, overGet is strict:

  • only queries can be sent this way, never mutations
  • the document must be known when the Simulation is built, so use query or file, not document
  • it can’t be combined with dynamicDocument or persisted queries
     
graphql.query("query GetUser($id: ID!) { user(id: $id) { name } }")
  .variable("id", 42)
  .overGet();
graphql.query("query GetUser(\$id: ID!) { user(id: \$id) { name } }")
  .variable("id", 42)
  .overGet()
graphql
  .query("query GetUser($id: ID!) { user(id: $id) { name } }")
  .variable("id", 42)
  .overGet

Checks

Use check to perform checks on the response. By default, Gatling checks the HTTP status and the errors of the response.

In addition to the usual HTTP checks, Gatling provides jsonPath and jmesPath checks rooted at a top level member of the GraphQL response:

  • graphqlData for the data member
  • graphqlErrors for the errors member
  • graphqlExtensions for the extensions member

For example, you can write $.user.name instead of $.data.user.name. These are regular jsonPath and jmesPath checks otherwise, so you can use the same criteria and saveAs.

     
graphql.file("graphql/getUser.graphql")
  .check(
    graphqlData.jsonPath("$.user.name").is("Stephane"),
    graphqlData.jsonPath("$.user.id").saveAs("userId"),
    graphqlData.jmesPath("user.name").exists(),
    graphqlErrors.jsonPath("$[0].extensions.code").optional().saveAs("errorCode"),
    graphqlExtensions.jsonPath("$.tracing.duration").optional(),
    // usual HTTP checks
    status().is(200)
  );
graphql.file("graphql/getUser.graphql")
  .check(
    graphqlData.jsonPath("$.user.name").`is`("Stephane"),
    graphqlData.jsonPath("$.user.id").saveAs("userId"),
    graphqlData.jmesPath("user.name").exists(),
    graphqlErrors.jsonPath("$[0].extensions.code").optional().saveAs("errorCode"),
    graphqlExtensions.jsonPath("$.tracing.duration").optional(),
    // usual HTTP checks
    status().`is`(200)
  )
graphql
  .file("graphql/getUser.graphql")
  .check(
    graphqlData.jsonPath("$.user.name").is("Stephane"),
    graphqlData.jsonPath("$.user.id").saveAs("userId"),
    graphqlData.jmesPath("user.name").exists,
    graphqlErrors.jsonPath("$[0].extensions.code").optional.saveAs("errorCode"),
    graphqlExtensions.jsonPath("$.tracing.duration").optional,
    // usual HTTP checks
    status.is(200)
  )

You can also apply a function on the Session resulting from the checks, after they’ve been applied, with postCheck. Throw an exception to make the request fail with the exception’s message (in Scala, the function returns a Validation: a Success wrapping the new Session, or a Failure).

     
graphql.file("graphql/getUser.graphql")
  .check(graphqlData.jsonPath("$.user.name").saveAs("userName"))
  .postCheck(session -> {
    if (!session.contains("userName")) {
      throw new IllegalStateException("userName is missing");
    }
    return session.set("greeting", "Hello " + session.getString("userName"));
  });
graphql.file("graphql/getUser.graphql")
  .check(graphqlData.jsonPath("$.user.name").saveAs("userName"))
  .postCheck { session ->
    if (!session.contains("userName")) {
      throw IllegalStateException("userName is missing")
    }
    session.set("greeting", "Hello " + session.getString("userName"))
  }
graphql
  .file("graphql/getUser.graphql")
  .check(graphqlData.jsonPath("$.user.name").saveAs("userName"))
  .postCheck(session => session("userName").validate[String].map(userName => session.set("greeting", s"Hello $userName")))

Edit this page on GitHub