Revise GraphQlTester documentation
Closes gh-317
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
include::attributes.adoc[]
|
||||
|
||||
[[testing]]
|
||||
= Testing
|
||||
|
||||
It's possible to test GraphQL requests with Spring's `WebTestClient`, just sending and
|
||||
receiving JSON, but a number of GraphQL specific details make this approach more
|
||||
cumbersome than is necessary.
|
||||
Spring for GraphQL provides dedicated support for testing GraphQL requests. It can send
|
||||
requests over HTTP or WebSocket using a client. It can also execute requests directly on
|
||||
the server side.
|
||||
|
||||
To get the full testing support, you'll need to add the `spring-graphql-test` dependdency
|
||||
in your build:
|
||||
To make use of it, add `spring-graphql-test` to your build:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes,attributes",role="primary"]
|
||||
.Gradle
|
||||
@@ -34,45 +35,30 @@ dependencies {
|
||||
[[testing-graphqltester]]
|
||||
== `GraphQlTester`
|
||||
|
||||
`GraphQlTester` defines a workflow to test GraphQL requests with the following benefits:
|
||||
`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.
|
||||
|
||||
- Verify no unexpected errors under the "errors" key in the response.
|
||||
- Decode under the "data" key in the response.
|
||||
- Use JsonPath to decode different parts of the response.
|
||||
- Test subscriptions.
|
||||
To test with a client sending requests to a server over a transport, use the
|
||||
<<testing-httpgraphqltester>> or the <<testing-websocketgraphqltester>> `GraphQlTester`
|
||||
extensions. For server side tests, executed without any client, use the
|
||||
<<testing-graphqlservicetester>> or the <<testing-webgraphqlhandlertester>> extensions.
|
||||
|
||||
To create `GraphQlTester`, you only need a `GraphQlService`, and no transport:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
GraphQlSource graphQlSource = GraphQlSource.builder()
|
||||
.schemaResources(...)
|
||||
.runtimeWiringConfigurer(...)
|
||||
.build();
|
||||
|
||||
GraphQlService graphQlService = new ExecutionGraphQlService(graphQlSource);
|
||||
|
||||
GraphQlTester graphQlTester = GraphQlTester.builder(graphQlService).build();
|
||||
----
|
||||
The main purpose of an extension is to provide a transport specific `Builder`. There is
|
||||
also a <<testing-graphqltester-builder>> in `GraphQlTester` with common configuration
|
||||
options that apply to any extension.
|
||||
|
||||
|
||||
[[testing-httpgraphqltester]]
|
||||
=== HTTP
|
||||
|
||||
[[testing-webgraphqltester]]
|
||||
== `WebGraphQlTester`
|
||||
`HttpGraphQlTester` wraps a
|
||||
{spring-framework-ref-docs}/testing.html#webtestclient[WebTestClient] and uses it to
|
||||
execute GraphQL requests over HTTP, with or without a live server, depending on how
|
||||
`WebTestClient` is configured.
|
||||
|
||||
`WebGraphQlTester` extends `GraphQlTester` to add a workflow and configuration specific
|
||||
to <<index#web-transports>>, and it always verifies GraphQL HTTP responses are 200 (OK).
|
||||
|
||||
|
||||
To create `WebGraphQlTester`, you need one of the following inputs:
|
||||
|
||||
- `WebTestClient` -- perform requests as an HTTP client, either against <<index#web-http>>
|
||||
handlers without a server, or against a live server.
|
||||
- `WebGraphQlHandler` -- perform requests through the <<index#web-interception>> chain used
|
||||
by both <<index#web-http>> and <<index#web-websocket>> handlers, which in effect is testing without
|
||||
a Web framework. One reason to use this is for <<testing-subscriptions>>.
|
||||
|
||||
For Spring WebFlux without a server, you can point to your Spring configuration:
|
||||
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"]
|
||||
----
|
||||
@@ -84,14 +70,14 @@ For Spring WebFlux without a server, you can point to your Spring configuration:
|
||||
.baseUrl("/graphql")
|
||||
.build();
|
||||
|
||||
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
|
||||
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
||||
----
|
||||
|
||||
For Spring MVC without a server, the same but using `MockMvcWebTestClient`:
|
||||
To test in Spring MVC, without a live server, do the same using `MockMvcWebTestClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebApplicationContext context = ... ;
|
||||
ApplicationContext context = ... ;
|
||||
|
||||
WebTestClient client =
|
||||
MockMvcWebTestClient.bindToApplicationContext(context)
|
||||
@@ -99,10 +85,10 @@ For Spring MVC without a server, the same but using `MockMvcWebTestClient`:
|
||||
.baseUrl("/graphql")
|
||||
.build();
|
||||
|
||||
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
|
||||
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
||||
----
|
||||
|
||||
To test against a live, running server:
|
||||
Or to test against a live server running on a port:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -111,45 +97,137 @@ To test against a live, running server:
|
||||
.baseUrl("http://localhost:8080/graphql")
|
||||
.build();
|
||||
|
||||
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
|
||||
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
|
||||
----
|
||||
|
||||
`WebGraphQlTester` supports setting HTTP request headers and access to HTTP response
|
||||
headers. This may be useful to inspect or set security related headers.
|
||||
The `HttpGraphQlTester` extension is nothing but a `GraphQlTester` with a specialized
|
||||
builder. Once created, it exposes the same transport agnostic workflow for request
|
||||
execution.
|
||||
|
||||
This means you can only configure HTTP request details at build time, and
|
||||
they 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"]
|
||||
----
|
||||
this.graphQlTester.queryName("{ myQuery }")
|
||||
.httpHeaders(headers -> headers.setBasicAuth("rob", "..."))
|
||||
.execute()
|
||||
.httpHeadersSatisfy(headers -> {
|
||||
// check response headers
|
||||
})
|
||||
.path("myQuery.field1").entity(String.class).isEqualTo("value1")
|
||||
.path("myQuery.field2").entity(String.class).isEqualTo("value2");
|
||||
HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder)
|
||||
.httpHeaders(headers -> headers.setBasicAuth("joe", "..."))
|
||||
.build();
|
||||
|
||||
// Use tester...
|
||||
|
||||
HttpGraphQlTester anotherTester = tester.mutate()
|
||||
.httpHeaders(headers -> headers.setBasicAuth("peter", "..."))
|
||||
.build();
|
||||
|
||||
// Use anotherTester...
|
||||
|
||||
----
|
||||
|
||||
You can also set default request headers at the builder level:
|
||||
|
||||
[[testing-websocketgraphqltester]]
|
||||
=== WebSocket
|
||||
|
||||
`WebSocketGraphQlTester` wraps the
|
||||
{spring-framework-ref-docs}/web-reactive.html#webflux-websocket-client[WebSocketClient]
|
||||
from Spring WebFlux and uses it to execute GraphQL requests over WebSocket. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebGraphQlTester tester = WebGraphQlTester.builder(client)
|
||||
.defaultHttpHeaders(headers -> headers.setBasicAuth("rob", "..."))
|
||||
.build();
|
||||
String url = "http://localhost:8080/graphql";
|
||||
WebSocketClient client = new ReactorNettyWebSocketClient();
|
||||
|
||||
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[testing-queries]]
|
||||
== Queries
|
||||
|
||||
Below is an example query test using
|
||||
https://github.com/json-path/JsonPath[JsonPath] to extract all release versions in the
|
||||
GraphQL response.
|
||||
Once created, `WebSocketGraphQlTester` exposes the same transport agnostic workflow for
|
||||
request execution. To change any transport details, use `mutate()` on an existing
|
||||
`WebSocketGraphQlTester` to create another with different configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String query = "{" +
|
||||
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(clientBuilder)
|
||||
.httpHeaders(headers -> headers.setBasicAuth("joe", "..."))
|
||||
.build();
|
||||
|
||||
// Use tester...
|
||||
|
||||
WebSocketGraphQlTester adminTester = tester.mutate()
|
||||
.httpHeaders(headers -> headers.setBasicAuth("peter", "..."))
|
||||
.build();
|
||||
|
||||
// Use anotherTester...
|
||||
|
||||
----
|
||||
|
||||
|
||||
[[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
|
||||
`GraphQlService`, use the `GraphQlServiceTester` extension:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
GraphQlService service = ... ;
|
||||
GraphQlServiceTester tester = GraphQlServiceTester.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
|
||||
`WebInterceptor` chain before handing off to `GraphQlService` 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)
|
||||
.httpHeaders(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
|
||||
|
||||
The below shows an example test that uses
|
||||
https://github.com/json-path/JsonPath[JsonPath] to extract all project release versions
|
||||
from the response:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String document = "{" +
|
||||
" project(slug:\"spring-framework\") {" +
|
||||
" releases {" +
|
||||
" version" +
|
||||
@@ -157,7 +235,7 @@ GraphQL response.
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
graphQlTester.query(query)
|
||||
graphQlTester.document(document)
|
||||
.execute()
|
||||
.path("project.releases[*].version")
|
||||
.entityList(String.class)
|
||||
@@ -166,9 +244,9 @@ GraphQL response.
|
||||
|
||||
The JsonPath is relative to the "data" section of the response.
|
||||
|
||||
You can also create query files with extensions `.graphql` or `.gql` under `"graphql/"` on
|
||||
the classpath and refer to them by file name. For example, given a file called
|
||||
`projectReleases.graphql` in `src/main/resources/graphql`, with content:
|
||||
You can also create document files with extensions `.graphql` or `.gql` under
|
||||
`"graphql/"` on the classpath and refer to them by file name. For example, given a file
|
||||
called `projectReleases.graphql` in `src/main/resources/graphql`, with content:
|
||||
|
||||
[source,graphql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -181,18 +259,18 @@ the classpath and refer to them by file name. For example, given a file called
|
||||
}
|
||||
----
|
||||
|
||||
You can write the same test as follows:
|
||||
You can then re-write the test:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
graphQlTester.queryName("projectReleases") <1>
|
||||
graphQlTester.documentName("projectReleases") <1>
|
||||
.variable("slug", "spring-framework") <2>
|
||||
.execute()
|
||||
.path("project.releases[*].version")
|
||||
.entityList(String.class)
|
||||
.hasSizeGreaterThan(1);
|
||||
----
|
||||
<1> Refer to the query in the file named "projectReleases".
|
||||
<1> Refer to the document in the file named "projectReleases".
|
||||
<2> Set the `slug` variable.
|
||||
|
||||
[TIP]
|
||||
@@ -200,14 +278,21 @@ You can write the same test as follows:
|
||||
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();
|
||||
----
|
||||
|
||||
|
||||
[[testing-errors]]
|
||||
== Errors
|
||||
|
||||
Verify won't succeed when there are errors under the "errors" key in the response.
|
||||
|
||||
If necessary to ignore an error, use an error filter `Predicate`:
|
||||
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"]
|
||||
----
|
||||
@@ -221,7 +306,7 @@ If necessary to ignore an error, use an error filter `Predicate`:
|
||||
.hasSizeGreaterThan(1);
|
||||
----
|
||||
|
||||
An error filter can be registered globally and apply to all tests:
|
||||
You can register an error filter at the builder level, to apply to all tests:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -230,9 +315,8 @@ An error filter can be registered globally and apply to all tests:
|
||||
.build();
|
||||
----
|
||||
|
||||
|
||||
Or to expect an error, and in contrast to `filter`, throw an assertion error
|
||||
when it doesn't exist in the response:
|
||||
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"]
|
||||
----
|
||||
@@ -246,7 +330,8 @@ when it doesn't exist in the response:
|
||||
.hasSizeGreaterThan(1);
|
||||
----
|
||||
|
||||
Or inspect all errors directly and that also marks them as filtered:
|
||||
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"]
|
||||
----
|
||||
@@ -258,64 +343,26 @@ Or inspect all errors directly and that also marks them as filtered:
|
||||
});
|
||||
----
|
||||
|
||||
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();
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[testing-subscriptions]]
|
||||
== Subscriptions
|
||||
|
||||
The `executeSubscription` method defines a workflow specific to subscriptions which return
|
||||
a stream of responses instead of a single response.
|
||||
|
||||
To test subscriptions, you can create `GraphQlTester` with a `GraphQlService`, which
|
||||
calls `graphql.GraphQL` directly and that returns a stream of responses:
|
||||
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"]
|
||||
----
|
||||
GraphQlService service = ... ;
|
||||
|
||||
GraphQlTester graphQlTester = GraphQlTester.builder(service).build();
|
||||
|
||||
Flux<String> result = graphQlTester.query("subscription { greetings }")
|
||||
Flux<String> greetingFlux = tester.document("subscription { greetings }")
|
||||
.executeSubscription()
|
||||
.toFlux("greetings", String.class); // decode each response
|
||||
----
|
||||
.toFlux("greetings", String.class); // decode at JSONPath
|
||||
|
||||
The `StepVerifier` from Project Reactor is useful to verify a stream:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Flux<String> result = graphQlTester.query("subscription { greetings }")
|
||||
.executeSubscription()
|
||||
.toFlux("greetings", String.class);
|
||||
|
||||
StepVerifier.create(result)
|
||||
StepVerifier.create(greetingFlux)
|
||||
.expectNext("Hi")
|
||||
.expectNext("Bonjour")
|
||||
.expectNext("Hola")
|
||||
.verifyComplete();
|
||||
----
|
||||
|
||||
To test with the <<index#web-interception>> chain, you can create `WebGraphQlTester` with a
|
||||
`WebGraphQlHandler`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
GraphQlService service = ... ;
|
||||
|
||||
WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
|
||||
.interceptor((input, next) -> next.handle(input))
|
||||
.build();
|
||||
|
||||
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler).build();
|
||||
----
|
||||
|
||||
Currently, Spring for GraphQL does not support testing with a WebSocket client, and it
|
||||
cannot be used for integration test of GraphQL over WebSocket requests.
|
||||
You can test subscriptions over WebSocket via <<testing-websocketgraphqltester>>, or
|
||||
without a client on the server side, through the <<testing-graphqlservicetester>>, or
|
||||
the <<testing-webgraphqlhandlertester>> extensions.
|
||||
Reference in New Issue
Block a user