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
.ignoreProtocolChecksendpoint: override the protocol endpoint for this request only.headerandheaders: set headers.requestTimeout: override the request timeout.silentandnotSilent: 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
queryorfile, notdocument - it can’t be combined with
dynamicDocumentor 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)
.overGetChecks
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:
graphqlDatafor thedatamembergraphqlErrorsfor theerrorsmembergraphqlExtensionsfor theextensionsmember
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")))