Add section on GraphQlClient to the reference docs

See gh-10
This commit is contained in:
rstoyanchev
2022-03-10 09:48:14 +00:00
parent f568bce8a5
commit fb521e9b6f
3 changed files with 298 additions and 59 deletions

View File

@@ -0,0 +1,215 @@
include::attributes.adoc[]
[[client]]
= Client
Spring for GraphQL includes client support for executing GraphQL requests over HTTP or
over WebSocket.
[[client-graphqlclient]]
== `GraphQlClient`
`GraphQlClient` defines a common workflow for executing GraphQL requests and
subscriptions that is independent of and agnostic to the underlying transport. To create
an instance, you'll need to start from either the <<client-httpgraphqlclient,
HttpGraphQlClient>> or the <<client-websocketgraphqlclient,WebSocketGraphQlClient>>
extensions.
The main purpose of a `GraphQlClient` extension is to provide a transport specific
`Builder`. There is also a common <<client-graphqlclient-builder>> in `GraphQlClient`
with configuration options that apply to any extension.
[[client-httpgraphqlclient]]
=== HTTP
`HttpGraphQlClient` uses
{spring-framework-ref-docs}/web-reactive.html#webflux-client[WebClient] to execute
GraphQL requests over HTTP.
[source,java,indent=0,subs="verbatim,quotes"]
----
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
----
The `HttpGraphQlClient` extension is nothing but a `GraphQlClient` with a specialized
builder. Once created, it exposes the same workflow for request execution 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 client instance. To change HTTP request
details, use `mutate()` on an existing `HttpGraphQlClient` to create another
instance with different configuration:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
----
[[client-websocketgraphqlclient]]
=== WebSocket
`WebSocketGraphQlClient` 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();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
----
Once created, `WebSocketGraphQlClient` exposes the same transport agnostic workflow for
request execution as any `GrahQlClient`. To change any transport details, use `mutate()`
on an existing `WebSocketGraphQlClient` to create another with different configuration:
[source,java,indent=0,subs="verbatim,quotes"]
----
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use graphQlClient...
WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherGraphQlClient...
----
`WebSocketGraphQlClient` exposes a `start()` method to connect the underlying WebSocket.
This can be used on startup up to prepare for requests, but it is not required. A
connection is established automatically when a request is made.
`WebSocketGraphQlClient` maintains only one connection at a time that is used in
multiplex style and shared for all requests through the client. If the connection is
lost, it is automatically re-established on the next request.
`WebSocketGraphQlClient` also exposes a `stop()` method that cancels ongoing
requests and subscriptions, and closes the connection. A stopped client rejects
new requests. Use `start()` to re-establish the connection and allow requests again.
[[client-graphqlclient-builder]]
=== Builder
`GraphQlClient` defines a parent `Builder` with common configuration options for the
builders of all extensions. Currently, it has lets you configure a `DocumentSource`,
which is a strategy for loading the document for a request by file name.
[[client-requests]]
== Requests
Once you have a `GraphQlClient`, you can begin to execute requests. The below executes
a query for a project and uses https://github.com/json-path/JsonPath[JsonPath] to
access the project from the response:
[source,java,indent=0,subs="verbatim,quotes"]
----
String document = "{" +
" project(slug:\"spring-framework\") {" +
" name" +
" releases {" +
" version" +
" }"+
" }" +
"}";
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map(response -> response.toEntity("project", Project.class));
----
The JsonPath is relative to the "data" section of the response.
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 `project.graphql` in `src/main/resources/graphql`, with content:
[source,graphql,indent=0,subs="verbatim,quotes"]
----
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
----
You can then use:
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Project> projectMono = graphQlClient.documentName("project") <1>
.variable("slug", "spring-framework") <2>
.execute()
.map(response -> response.toEntity("project", Project.class));
----
<1> Refer to the document in the file named "projectReleases".
<2> Set the `slug` variable.
[TIP]
====
The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion.
====
[[client-subscriptions]]
== Subscriptions
To start a subscription, call `executeSubscription` instead of `execute` to obtain a
stream of responses rather than a single response:
[source,java,indent=0,subs="verbatim,quotes"]
----
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode at JSONPath
----
Subscriptions are supported only with <<client-websocketgraphqlclient,
WebSocketGraphQlClient>>.

View File

@@ -1484,13 +1484,16 @@ The Spring for GraphQL repository contains samples for
include::client.adoc[leveloffset=+1]
include::testing.adoc[leveloffset=+1]
[[samples]]
== Samples

View File

@@ -1,13 +1,15 @@
include::attributes.adoc[]
[[testing]]
= Testing
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.
Spring for GraphQL provides dedicated support for testing GraphQL requests over HTTP or
WebSocket, and for testing GraphQL requests executed directly against a server.
To make use of it, add `spring-graphql-test` to your build:
To make use of this, add `spring-graphql-test` to your build:
[source,groovy,indent=0,subs="verbatim,quotes,attributes",role="primary"]
.Gradle
@@ -32,6 +34,8 @@ dependencies {
----
[[testing-graphqltester]]
== `GraphQlTester`
@@ -44,17 +48,18 @@ To test with a client sending requests to a server over a transport, use the
extensions. For server side tests, executed without any client, use the
<<testing-graphqlservicetester>> or the <<testing-webgraphqlhandlertester>> extensions.
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.
The main purpose of a `GraphQlTester` 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
`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
`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
@@ -101,24 +106,24 @@ Or to test against a live server running on a port:
----
The `HttpGraphQlTester` extension is nothing but a `GraphQlTester` with a specialized
builder. Once created, it exposes the same transport agnostic workflow for request
execution.
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
they apply to all requests through that Tester instance. To change HTTP request
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)
.httpHeaders(headers -> headers.setBasicAuth("joe", "..."))
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
HttpGraphQlTester anotherTester = tester.mutate()
.httpHeaders(headers -> headers.setBasicAuth("peter", "..."))
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherTester...
@@ -126,41 +131,48 @@ instance with different configuration:
----
[[testing-websocketgraphqltester]]
=== WebSocket
`WebSocketGraphQlTester` wraps the
`WebSocketGraphQlTester` uses
{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:
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();
String url = "http://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
----
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:
request execution as any `GrahQlTeste`. To change any transport details, use `mutate()`
on an existing `WebSocketGraphQlTester` to create another with different configuration:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(clientBuilder)
.httpHeaders(headers -> headers.setBasicAuth("joe", "..."))
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
WebSocketGraphQlTester adminTester = tester.mutate()
.httpHeaders(headers -> headers.setBasicAuth("peter", "..."))
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-graphqlservicetester]]
=== `GraphQlService`
@@ -176,6 +188,7 @@ client to send requests over a transport protocol. To test directly against a
----
[[testing-webgraphqlhandlertester]]
=== `WebGraphQlHandler`
@@ -199,11 +212,12 @@ The builder for this extension allows you to define HTTP request details:
WebGraphQlHandler handler = ... ;
WebGraphQlHandlerTester tester = WebGraphQlHandlerTester.builder(handler)
.httpHeaders(headers -> headers.setBasicAuth("joe", "..."))
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
----
[[testing-graphqltester-builder]]
=== Builder
@@ -218,12 +232,14 @@ the classpath or from anywhere else.
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:
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"]
----
@@ -259,7 +275,7 @@ called `projectReleases.graphql` in `src/main/resources/graphql`, with content:
}
----
You can then re-write the test:
You can then use:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -270,7 +286,7 @@ You can then re-write the test:
.entityList(String.class)
.hasSizeGreaterThan(1);
----
<1> Refer to the document in the file named "projectReleases".
<1> Refer to the document in the file named "project".
<2> Set the `slug` variable.
[TIP]
@@ -286,6 +302,35 @@ instead of `execute` to verify there are no errors in the response:
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
@@ -342,27 +387,3 @@ filtered, so you can then also inspect the data in the response:
// ...
});
----
[[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();
----
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.