412 lines
11 KiB
Plaintext
412 lines
11 KiB
Plaintext
include::attributes.adoc[]
|
|
|
|
|
|
|
|
|
|
[[testing]]
|
|
= Testing
|
|
|
|
Spring for GraphQL provides dedicated support for testing GraphQL requests over HTTP,
|
|
WebSocket, and RSocket, as well as for testing directly against a server.
|
|
|
|
To make use of this, add `spring-graphql-test` to your build:
|
|
|
|
[source,groovy,indent=0,subs="verbatim,quotes,attributes",role="primary"]
|
|
.Gradle
|
|
----
|
|
dependencies {
|
|
// ...
|
|
testImplementation 'org.springframework.graphql:spring-graphql-test:{spring-graphql-version}'
|
|
}
|
|
----
|
|
[source,xml,indent=0,subs="verbatim,quotes,attributes",role="secondary"]
|
|
.Maven
|
|
----
|
|
<dependencies>
|
|
<!-- ... -->
|
|
<dependency>
|
|
<groupId>org.springframework.graphql</groupId>
|
|
<artifactId>spring-graphql-test</artifactId>
|
|
<version>{spring-graphql-version}</version>
|
|
<scope>test</scope>
|
|
</dependency>
|
|
</dependencies>
|
|
----
|
|
|
|
|
|
|
|
|
|
[[testing-graphqltester]]
|
|
== `GraphQlTester`
|
|
|
|
`GraphQlTester` defines a common workflow for testing GraphQL requests. It is
|
|
independent of and agnostic to the underlying transport. To create an instance, you'll
|
|
need to choose a specific `GraphQlTester` extension as a starting point.
|
|
|
|
To test with a client sending requests over a transport, use one of the
|
|
<<testing-httpgraphqltester>>, <<testing-websocketgraphqltester>>, or
|
|
<<testing-rsocketgraphqltester>> extensions. To test the server without client,
|
|
use the<<testing-graphqlservicetester>> or <<testing-webgraphqlhandlertester>>
|
|
extensions.
|
|
|
|
Each `GraphQlTester` extension provides a transport specific `Builder`. There is also a
|
|
shared, base <<testing-graphqltester-builder>> in `GraphQlTester` with common options
|
|
for all extensions.
|
|
|
|
|
|
|
|
[[testing-httpgraphqltester]]
|
|
=== HTTP
|
|
|
|
`HttpGraphQlTester` uses
|
|
{spring-framework-ref-docs}/testing.html#webtestclient[WebTestClient] to execute
|
|
GraphQL requests over HTTP, with or without a live server, depending on how
|
|
`WebTestClient` is configured.
|
|
|
|
To test in Spring WebFlux, without a live server, point to your Spring configuration
|
|
that declares the GraphQL HTTP endpoint:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
ApplicationContext context = ... ;
|
|
|
|
WebTestClient client =
|
|
WebTestClient.bindToApplicationContext(context)
|
|
.configureClient()
|
|
.baseUrl("/graphql")
|
|
.build();
|
|
|
|
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
|
----
|
|
|
|
To test in Spring MVC, without a live server, do the same using `MockMvcWebTestClient`:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
ApplicationContext context = ... ;
|
|
|
|
WebTestClient client =
|
|
MockMvcWebTestClient.bindToApplicationContext(context)
|
|
.configureClient()
|
|
.baseUrl("/graphql")
|
|
.build();
|
|
|
|
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
|
----
|
|
|
|
Or to test against a live server running on a port:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
WebTestClient client =
|
|
WebTestClient.bindToServer()
|
|
.baseUrl("http://localhost:8080/graphql")
|
|
.build();
|
|
|
|
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
|
----
|
|
|
|
The `HttpGraphQlTester` extension is nothing but a `GraphQlTester` with a specialized
|
|
builder. Once created, it exposes the same workflow for testing requests that is
|
|
independent of the underlying transport.
|
|
|
|
This means you can only configure HTTP request details at build time, and
|
|
those apply to all requests through that Tester instance. To change HTTP request
|
|
details, use `mutate()` on an existing `HttpGraphQlTester` to create another
|
|
instance with different configuration:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder)
|
|
.headers(headers -> headers.setBasicAuth("joe", "..."))
|
|
.build();
|
|
|
|
// Use tester...
|
|
|
|
HttpGraphQlTester anotherTester = tester.mutate()
|
|
.headers(headers -> headers.setBasicAuth("peter", "..."))
|
|
.build();
|
|
|
|
// Use anotherTester...
|
|
|
|
----
|
|
|
|
|
|
|
|
[[testing-websocketgraphqltester]]
|
|
=== WebSocket
|
|
|
|
`WebSocketGraphQlTester` uses
|
|
{spring-framework-ref-docs}/web-reactive.html#webflux-websocket-client[WebSocketClient]
|
|
from Spring WebFlux to execute GraphQL requests over WebSocket:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
String url = "http://localhost:8080/graphql";
|
|
WebSocketClient client = new ReactorNettyWebSocketClient();
|
|
|
|
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
|
|
----
|
|
|
|
Once created, `WebSocketGraphQlTester` exposes the same transport agnostic workflow for
|
|
request execution as any `GraphQlTester`. To change any transport details, use `mutate()`
|
|
on an existing `WebSocketGraphQlTester` to create another with different configuration:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
URI url = ... ;
|
|
WebSocketClient client = ... ;
|
|
|
|
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client)
|
|
.headers(headers -> headers.setBasicAuth("joe", "..."))
|
|
.build();
|
|
|
|
// Use tester...
|
|
|
|
WebSocketGraphQlTester anotherTester = tester.mutate()
|
|
.headers(headers -> headers.setBasicAuth("peter", "..."))
|
|
.build();
|
|
|
|
// Use anotherTester...
|
|
----
|
|
|
|
`WebSocketGraphQlTester` provides a `stop()` method that you can use to have the WebSocket
|
|
connection closed, e.g. after a test runs.
|
|
|
|
|
|
|
|
[[testing-rsocketgraphqltester]]
|
|
=== RSocket
|
|
|
|
`RSocketGraphQlTester` uses `RSocketRequester` from spring-messaging to execute GraphQL
|
|
requests over RSocket:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
RSocketGraphQlTester tester = RSocketGraphQlTester.builder()
|
|
.webSocket(URI.create("http://localhost:8080/graphql"))
|
|
.build();
|
|
----
|
|
|
|
Once created, `RSocketGraphQlTester` exposes the same transport agnostic workflow for
|
|
request execution as any `GraphQlTester`.
|
|
|
|
|
|
|
|
[[testing-graphqlservicetester]]
|
|
=== `GraphQlService`
|
|
|
|
Many times it's enough to test GraphQL requests on the server side, without the use of a
|
|
client to send requests over a transport protocol. To test directly against a
|
|
`ExecutionGraphQlService`, use the `ExecutionGraphQlServiceTester` extension:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
GraphQlService service = ... ;
|
|
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service);
|
|
----
|
|
|
|
|
|
|
|
[[testing-webgraphqlhandlertester]]
|
|
=== `WebGraphQlHandler`
|
|
|
|
The <<testing-graphqlservicetester>> extension lets you test on the server side, without
|
|
a client. However, in some cases it's useful to involve server side transport
|
|
handling with given mock transport input.
|
|
|
|
The `WebGraphQlHandlerTester` extension lets you processes request through the
|
|
`WebGraphQlInterceptor` chain before handing off to `ExecutionGraphQlService` for
|
|
request execution:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
WebGraphQlHandler handler = ... ;
|
|
WebGraphQlHandlerTester tester = WebGraphQlHandlerTester.create(handler);
|
|
----
|
|
|
|
The builder for this extension allows you to define HTTP request details:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
WebGraphQlHandler handler = ... ;
|
|
|
|
WebGraphQlHandlerTester tester = WebGraphQlHandlerTester.builder(handler)
|
|
.headers(headers -> headers.setBasicAuth("joe", "..."))
|
|
.build();
|
|
----
|
|
|
|
|
|
|
|
[[testing-graphqltester-builder]]
|
|
=== Builder
|
|
|
|
`GraphQlTester` defines a parent `Builder` with common configuration options for the
|
|
builders of all extensions. It lets you configure the following:
|
|
|
|
- `errorFilter` - a predicate to suppress expected errors, so you can inspect the data
|
|
of the response.
|
|
- `documentSource` - a strategy for loading the document for a request from a file on
|
|
the classpath or from anywhere else.
|
|
- `responseTimeout` - how long to wait for request execution to complete before timing
|
|
out.
|
|
|
|
|
|
|
|
|
|
[[testing-requests]]
|
|
== Requests
|
|
|
|
Once you have a `GraphQlTester`, you can begin to test requests. The below executes a
|
|
query for a project and uses https://github.com/json-path/JsonPath[JsonPath] to extract
|
|
project release versions from the response:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
String document = "{" +
|
|
" project(slug:\"spring-framework\") {" +
|
|
" releases {" +
|
|
" version" +
|
|
" }"+
|
|
" }" +
|
|
"}";
|
|
|
|
graphQlTester.document(document)
|
|
.execute()
|
|
.path("project.releases[*].version")
|
|
.entityList(String.class)
|
|
.hasSizeGreaterThan(1);
|
|
----
|
|
|
|
The JsonPath is relative to the "data" section of the response.
|
|
|
|
You can also create document files with extensions `.graphql` or `.gql` under
|
|
`"graphql-test/"` on the classpath and refer to them by file name.
|
|
|
|
For example, given a file called `projectReleases.graphql` in
|
|
`src/main/resources/graphql-test`, with content:
|
|
|
|
[source,graphql,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
query projectReleases($slug: ID!) {
|
|
project(slug: $slug) {
|
|
releases {
|
|
version
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
You can then use:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
graphQlTester.documentName("projectReleases") <1>
|
|
.variable("slug", "spring-framework") <2>
|
|
.execute()
|
|
.path("project.releases[*].version")
|
|
.entityList(String.class)
|
|
.hasSizeGreaterThan(1);
|
|
----
|
|
<1> Refer to the document in the file named "project".
|
|
<2> Set the `slug` variable.
|
|
|
|
[TIP]
|
|
====
|
|
The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion.
|
|
====
|
|
|
|
If a request does not have any response data, e.g. mutation, use `executeAndVerify`
|
|
instead of `execute` to verify there are no errors in the response:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
graphQlTester.query(query).executeAndVerify();
|
|
----
|
|
|
|
See <<testing-errors>> for more details on error handling.
|
|
|
|
|
|
|
|
|
|
[[testing-subscriptions]]
|
|
== Subscriptions
|
|
|
|
To test subscriptions, call `executeSubscription` instead of `execute` to obtain a stream
|
|
of responses and then use `StepVerifier` from Project Reactor to inspect the stream:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
Flux<String> greetingFlux = tester.document("subscription { greetings }")
|
|
.executeSubscription()
|
|
.toFlux("greetings", String.class); // decode at JSONPath
|
|
|
|
StepVerifier.create(greetingFlux)
|
|
.expectNext("Hi")
|
|
.expectNext("Bonjour")
|
|
.expectNext("Hola")
|
|
.verifyComplete();
|
|
----
|
|
|
|
Subscriptions are supported only with <<testing-websocketgraphqltester,
|
|
WebSocketGraphQlTester>>, or with the server side
|
|
<<testing-graphqlservicetester>> and <<testing-webgraphqlhandlertester>> extensions.
|
|
|
|
|
|
|
|
[[testing-errors]]
|
|
== Errors
|
|
|
|
When you use `verify()`, any errors under the "errors" key in the response will cause
|
|
an assertion failure. To suppress a specific error, use the error filter before
|
|
`verify()`:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
graphQlTester.query(query)
|
|
.execute()
|
|
.errors()
|
|
.filter(error -> ...)
|
|
.verify()
|
|
.path("project.releases[*].version")
|
|
.entityList(String.class)
|
|
.hasSizeGreaterThan(1);
|
|
----
|
|
|
|
You can register an error filter at the builder level, to apply to all tests:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
|
|
.errorFilter(error -> ...)
|
|
.build();
|
|
----
|
|
|
|
If you want to verify that an error does exist, and in contrast to `filter`, throw an
|
|
assertion error if it doesn't, then use `exepect` instead:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
graphQlTester.query(query)
|
|
.execute()
|
|
.errors()
|
|
.expect(error -> ...)
|
|
.verify()
|
|
.path("project.releases[*].version")
|
|
.entityList(String.class)
|
|
.hasSizeGreaterThan(1);
|
|
----
|
|
|
|
You can also inspect all errors through a `Consumer`, and doing so also marks them as
|
|
filtered, so you can then also inspect the data in the response:
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
graphQlTester.query(query)
|
|
.execute()
|
|
.errors()
|
|
.satisfy(errors -> {
|
|
// ...
|
|
});
|
|
----
|