Files
spring-graphql/spring-graphql-docs/src/docs/asciidoc/testing.adoc
rstoyanchev 696f2cd02f Refine documentSource locations
Closes gh-338
2022-04-01 21:39:42 +01:00

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 -> {
// ...
});
----