GraphQL Subscriptions
Learn how to run GraphQL subscriptions over WebSocket, and check the messages they push.
Gatling supports GraphQL subscriptions over WebSocket with the graphql-transport-ws protocol.
A virtual user has one WebSocket, over which it can multiplex several subscriptions. Each subscription carries an id and messages are matched on it.
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._You must register the GraphQL protocol and configure wsBaseUrl on the HTTP protocol.
The WebSocket path is the protocol wsEndpoint.
Connect
Use graphqlWs.connect to open the WebSocket and perform the connection_init / connection_ack handshake.
You must connect before you subscribe.
exec(graphqlWs.connect());
exec(graphqlWs.connect()
.requestName("Open GraphQL WebSocket")
.endpoint("/other/graphql")
.connectionInitPayload("authorization", "Bearer #{token}")
.connectionInitPayload(Map.of("locale", "en"))
.ackTimeout(Duration.ofSeconds(5)));
exec(graphqlWs.connect()
.connectionInitPayloadJson("{\"authorization\": \"Bearer #{token.jsonStringify()}\"}"));exec(graphqlWs.connect())
exec(graphqlWs.connect()
.requestName("Open GraphQL WebSocket")
.endpoint("/other/graphql")
.connectionInitPayload("authorization", "Bearer #{token}")
.connectionInitPayload(mapOf("locale" to "en"))
.ackTimeout(Duration.ofSeconds(5)))
exec(graphqlWs.connect()
.connectionInitPayloadJson("""{"authorization": "Bearer #{token.jsonStringify()}"}"""))exec(graphqlWs.connect)
exec(
graphqlWs.connect
.requestName("Open GraphQL WebSocket")
.endpoint("/other/graphql")
.connectionInitPayload("authorization", "Bearer #{token}")
.connectionInitPayload(Map("locale" -> "en"))
.ackTimeout(5.seconds)
)
exec(
graphqlWs.connect
.connectionInitPayloadJson("""{"authorization": "Bearer #{token.jsonStringify()}"}""")
)requestName: override the request name.endpoint: override the protocolwsEndpoint.connectionInitPayload: set entries of theconnection_initmessage payload, typically where the server expects credentials.connectionInitPayloadJson: set the whole payload at once as a JSON object that can contain Gatling Expression Language placeholders. It can’t be combined withconnectionInitPayload.ackTimeout: override how long to wait for theconnection_ackmessage (default: 10 seconds).
Gatling reports the handshake under the names graphql-ws connect, connection_init and connection_ack.
Subscribe
Use subscribe to subscribe with an inline document, or subscribeFile to load the document from a classpath resource.
The document must declare a subscription and be known when the Simulation is built.
Like requests, a subscription supports requestName, operationName, variable, variables and variablesJson.
exec(graphqlWs
.subscribe("subscription OrderEvents($id: ID!) { orderCreated(customerId: $id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders"));
exec(graphqlWs.subscribeFile("graphql/orderEvents.graphql"));exec(graphqlWs
.subscribe("subscription OrderEvents(\$id: ID!) { orderCreated(customerId: \$id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders"))
exec(graphqlWs.subscribeFile("graphql/orderEvents.graphql"))exec(
graphqlWs
.subscribe("subscription OrderEvents($id: ID!) { orderCreated(customerId: $id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders")
)
exec(graphqlWs.subscribeFile("graphql/orderEvents.graphql"))Use subscriptionName to set the id the subscription is registered under, which unsubscribe needs.
It defaults to the operation name.
A virtual user can’t have two active subscriptions with the same id: give each concurrent subscription its own subscriptionName.
Wait for messages
By default, subscribe doesn’t wait for any message.
Use await to wait for messages and checkNext to define the checks to perform on each of them.
Each checkNext expects one single next message, and messages are reported on their own, under the name <request name> next.
Gatling measures the response time of each message from the message that came before it.
It’s the inter message latency rather than the time since the subscription started.
exec(graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(Duration.ofSeconds(10)).on(
graphqlWs.checkNext()
.check(
graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"),
graphqlWs.errors.jsonPath("$[0].message").optional()
)
));exec(graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(Duration.ofSeconds(10)).on(
graphqlWs.checkNext()
.check(
graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"),
graphqlWs.errors.jsonPath("$[0].message").optional()
)
))exec(
graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(10.seconds)(
graphqlWs.checkNext
.check(
graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"),
graphqlWs.errors.jsonPath("$[0].message").optional
)
)
)checkNextaccepts a name to report the message under.checkaccepts the same criteria as GraphQL requests, except that you usegraphqlWs.dataandgraphqlWs.errorsin place ofgraphqlDataandgraphqlErrors.postCheckapplies a function on the Session resulting from the checks.silentdoesn’t report the message in the statistics.
The error policy of the protocol applies to the messages.
Use awaitNext to wait for some more messages, with a timeout for each of them.
exec(graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(Duration.ofSeconds(10)).on(graphqlWs.checkNext("first event"))
// wait for 5 more messages
.awaitNext(Duration.ofSeconds(30), 5));exec(graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(Duration.ofSeconds(10)).on(graphqlWs.checkNext("first event"))
// wait for 5 more messages
.awaitNext(Duration.ofSeconds(30), 5))exec(
graphqlWs
.subscribeFile("graphql/orderEvents.graphql")
.await(10.seconds)(graphqlWs.checkNext("first event"))
// wait for 5 more messages
.awaitNext(30.seconds, 5)
)complete or an error message that goes through a check, Gatling considers the subscription is no longer active.Unsubscribe
Use unsubscribe with the subscription name to tell the server to stop the subscription.
By default, Gatling reports it under the name subscription <subscription name> complete.
exec(graphqlWs.unsubscribe("orders"));
exec(graphqlWs.unsubscribe("orders").requestName("Stop orders"));exec(graphqlWs.unsubscribe("orders"))
exec(graphqlWs.unsubscribe("orders").requestName("Stop orders"))exec(graphqlWs.unsubscribe("orders"))
exec(graphqlWs.unsubscribe("orders").requestName("Stop orders"))Close
Use close to close the WebSocket. Gatling reports it under the name graphql-ws close.
exec(graphqlWs.close());exec(graphqlWs.close())exec(graphqlWs.close)Reconnection
When Gatling reconnects the WebSocket, it performs the handshake again and subscribes again to the subscriptions that were active, the way real clients do, as the server forgot about them.
Example
scenario("Subscriptions").exec(
graphqlWs.connect().connectionInitPayload("authorization", "Bearer #{token}"),
graphqlWs
.subscribe("subscription OrderEvents($id: ID!) { orderCreated(customerId: $id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders")
.await(Duration.ofSeconds(10)).on(
graphqlWs.checkNext().check(graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"))
)
.awaitNext(Duration.ofSeconds(30), 3),
graphqlWs.unsubscribe("orders"),
graphqlWs.close()
);scenario("Subscriptions").exec(
graphqlWs.connect().connectionInitPayload("authorization", "Bearer #{token}"),
graphqlWs
.subscribe("subscription OrderEvents(\$id: ID!) { orderCreated(customerId: \$id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders")
.await(Duration.ofSeconds(10)).on(
graphqlWs.checkNext().check(graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"))
)
.awaitNext(Duration.ofSeconds(30), 3),
graphqlWs.unsubscribe("orders"),
graphqlWs.close()
)scenario("Subscriptions").exec(
graphqlWs.connect.connectionInitPayload("authorization", "Bearer #{token}"),
graphqlWs
.subscribe("subscription OrderEvents($id: ID!) { orderCreated(customerId: $id) { id } }")
.variable("id", "#{customerId}")
.subscriptionName("orders")
.await(10.seconds)(
graphqlWs.checkNext.check(graphqlWs.data.jsonPath("$.orderCreated.id").saveAs("orderId"))
)
.awaitNext(30.seconds, 3),
graphqlWs.unsubscribe("orders"),
graphqlWs.close
)