From 311531b74fc6eaede33619a4cb9814d421d0e84f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 21 Aug 2024 10:44:54 +0200 Subject: [PATCH] Move client and testing docs to compiled code snippets Prior to this commit, the "client" and "testing" sections of the reference documentation were using inline code snippets. Because of this, several snippets were out of date or invalid. This commit moves all those code snippets to actual Java classes compiled with the documentation. Fixes gh-1042 --- spring-graphql-docs/build.gradle | 20 +- .../modules/ROOT/pages/client.adoc | 318 ++---------------- .../modules/ROOT/pages/testing.adoc | 256 ++++---------- .../client/httpgraphqlclient/ClientUsage.java | 48 +++ .../SyncClientUsage.java | 48 +++ .../client/interception/MyInterceptor.java | 40 +++ .../client/interception/SyncInterceptor.java | 30 ++ .../client/interception/UseInterceptor.java | 37 ++ .../documentsource/DocumentSource.java | 36 ++ .../docs/client/requests/execute/Execute.java | 80 +++++ .../client/requests/retrieve/Retrieve.java | 114 +++++++ .../RSocketClientUsage.java | 37 ++ .../execute/ExecuteSubscription.java | 50 +++ .../retrieve/RetrieveSubscription.java | 33 ++ .../WebSocketClientUsage.java | 67 ++++ .../docs/testing/errors/TestErrors.java | 79 +++++ .../graphqlservicetester/ServiceSetup.java | 42 +++ .../testing/httpgraphqltester/HttpSetup.java | 88 +++++ .../docs/testing/requests/TesterRequests.java | 72 ++++ .../requests/nestedpaths/NestedPaths.java | 34 ++ .../rsocketgraphqltester/RSocketSetup.java | 37 ++ .../subscriptions/TestSubscriptions.java | 40 +++ .../testing/webgraphqltester/WebSetup.java | 39 +++ .../websocketgraphqltester/WsSetup.java | 54 +++ 24 files changed, 1204 insertions(+), 495 deletions(-) create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java diff --git a/spring-graphql-docs/build.gradle b/spring-graphql-docs/build.gradle index f5e32712..35b9580d 100644 --- a/spring-graphql-docs/build.gradle +++ b/spring-graphql-docs/build.gradle @@ -19,14 +19,18 @@ dependencies { dependencyManagement(enforcedPlatform(dependencies.project(path: ":platform"))) api project(':spring-graphql') api project(':spring-graphql-test') - api 'org.springframework:spring-webflux' - api 'org.springframework:spring-webmvc' - api 'org.springframework:spring-websocket' - api 'org.springframework:spring-messaging' - api 'org.springframework.data:spring-data-commons' - api 'com.querydsl:querydsl-core' - api "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}" - api "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + implementation 'org.springframework:spring-webflux' + implementation 'org.springframework:spring-webmvc' + implementation 'org.springframework:spring-websocket' + implementation 'org.springframework:spring-messaging' + implementation 'org.springframework.data:spring-data-commons' + implementation 'com.querydsl:querydsl-core' + implementation "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}" + implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}" + implementation 'io.rsocket:rsocket-core' + implementation 'io.rsocket:rsocket-transport-netty' + implementation 'io.projectreactor:reactor-test' + implementation 'org.assertj:assertj-core' } jar { diff --git a/spring-graphql-docs/modules/ROOT/pages/client.adoc b/spring-graphql-docs/modules/ROOT/pages/client.adoc index 2270702c..21aee2b5 100644 --- a/spring-graphql-docs/modules/ROOT/pages/client.adoc +++ b/spring-graphql-docs/modules/ROOT/pages/client.adoc @@ -40,35 +40,14 @@ above `GraphQlClient` extensions. to execute GraphQL requests over HTTP through a blocking transport contract and chain of interceptors. -[source,java,indent=0,subs="verbatim,quotes"] ----- -RestClient restClient = ... ; -HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient); ----- +include-code::SyncClientUsage[tag=create,indent=0] Once `HttpSyncGraphQlClient` is created, you can begin to xref:client.adoc#client.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpSyncGraphQlClient` to create a new instance with customized settings: -[source,java,indent=0,subs="verbatim,quotes"] ----- - RestClient restClient = ... ; - - HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); - - // Perform requests with graphQlClient... - - HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate() - .headers(headers -> headers.setBasicAuth("peter", "...")) - .build(); - - // Perform requests with anotherGraphQlClient... - ----- - +include-code::SyncClientUsage[tag=mutate,indent=0] [[client.httpgraphqlclient]] @@ -79,35 +58,14 @@ existing `HttpSyncGraphQlClient` to create a new instance with customized settin GraphQL requests over HTTP through a non-blocking transport contract and chain of interceptors. -[source,java,indent=0,subs="verbatim,quotes"] ----- -WebClient webClient = ... ; -HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient); ----- +include-code::ClientUsage[tag=create,indent=0] Once `HttpGraphQlClient` is created, you can begin to xref:client.adoc#client.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpGraphQlClient` to create a new instance with customized settings: -[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... - ----- - +include-code::ClientUsage[tag=mutate,indent=0] [[client.websocketgraphqlclient]] @@ -118,13 +76,7 @@ It is built using the {spring-framework-ref-docs}/web/webflux-websocket.html#webflux-websocket-client[WebSocketClient] from Spring WebFlux and you can create it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - String url = "wss://localhost:8080/graphql"; - WebSocketClient client = new ReactorNettyWebSocketClient(); - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build(); ----- +include-code::WebSocketClientUsage[tag=create,indent=0] In contrast to `HttpGraphQlClient`, the `WebSocketGraphQlClient` is connection oriented, which means it needs to establish a connection before making any requests. As you begin @@ -146,37 +98,12 @@ xref:client.adoc#client.requests[execute requests] using the same API, independe transport. If you need to change any transport specific details, use `mutate()` on an existing `WebSocketGraphQlClient` to create a new instance with customized settings: -[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... - ----- +include-code::WebSocketClientUsage[tag=mutate,indent=0] `WebSocketGraphQlClient` supports sending periodic ping messages to keep the connection active when no other messages are sent or received. You can enable that as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) - .keepAlive(Duration.ofSeconds(30)) - .build(); ----- +include-code::WebSocketClientUsage[tag=keepAlive,indent=0] [[client.websocketgraphqlclient.interceptor]] @@ -220,15 +147,7 @@ can be at most one interceptor of type `WebSocketGraphQlClientInterceptor`. {spring-framework-ref-docs}/rsocket.html#rsocket-requester[RSocketRequester] to execute GraphQL requests over RSocket requests. -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI uri = URI.create("wss://localhost:8080/rsocket"); - WebsocketClientTransport transport = WebsocketClientTransport.create(url); - - RSocketGraphQlClient client = RSocketGraphQlClient.builder() - .clientTransport(transport) - .build(); ----- +include-code::RSocketClientUsage[tag=create,indent=0] In contrast to `HttpGraphQlClient`, the `RSocketGraphQlClient` is connection oriented, which means it needs to establish a session before making any requests. As you begin @@ -266,7 +185,6 @@ builders of all extensions. Currently, it has lets you configure: - [[client.requests]] == Requests @@ -287,36 +205,14 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - String document = "{" + - " project(slug:\"spring-framework\") {" + - " name" + - " releases {" + - " version" + - " }"+ - " }" + - "}"; - - Project project = graphQlClient.document(document) <1> - .retrieveSync("project") <2> - .toEntity(Project.class); <3> +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=retrieveSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - String document = "{" + - " project(slug:\"spring-framework\") {" + - " name" + - " releases {" + - " version" + - " }"+ - " }" + - "}"; - - Mono projectMono = graphQlClient.document(document) <1> - .retrieve("project") <2> - .toEntity(Project.class); <3> +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=retrieve,indent=0] ---- ====== @@ -342,32 +238,14 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - try { - Project project = graphQlClient.document(document) - .retrieveSync("project") - .toEntity(Project.class); - } - catch (FieldAccessException ex) { - ClientGraphQlResponse response = ex.getResponse(); - // ... - ClientResponseField field = ex.getField(); - // ... - } +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=fieldErrorSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - Mono projectMono = graphQlClient.document(document) - .retrieve("project") - .toEntity(Project.class) - .onErrorResume(FieldAccessException.class, ex -> { - ClientGraphQlResponse response = ex.getResponse(); - // ... - ClientResponseField field = ex.getField(); - // ... - }); +include::{include-java}/client/requests/retrieve/Retrieve.java[tag=fieldError,indent=0] ---- ====== @@ -387,54 +265,20 @@ Sync:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - ClientGraphQlResponse response = graphQlClient.document(document).executeSync(); - - if (!response.isValid()) { - // Request failure... <1> - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... <2> - } - else { - // Optional field set to null... <3> - } - } - - Project project = field.toEntity(Project.class); <4> +include::{include-java}/client/requests/execute/Execute.java[tag=executeSync,indent=0] ---- Non-Blocking:: + [source,java,indent=0,subs="verbatim,quotes",role="secondary"] ---- - Mono projectMono = graphQlClient.document(document) - .execute() - .map(response -> { - if (!response.isValid()) { - // Request failure... <1> - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... <2> - } - else { - // Optional field set to null... <3> - } - } - - return field.toEntity(Project.class); <4> - }); +include::{include-java}/client/requests/execute/Execute.java[tag=execute,indent=0] ---- ====== <1> The response does not have data, only errors -<2> Field that is `null` and has an associated error -<3> Field that was set to `null` by its `DataFetcher` +<2> Field that was set to `null` by its `DataFetcher` +<3> Field that is `null` and has an associated error <4> Decode the data at the given path @@ -468,63 +312,12 @@ You can then: [source,java,indent=0,subs="verbatim,quotes"] ---- - Project project = graphQlClient.documentName("projectReleases") <1> - .variable("slug", "spring-framework") <2> - .retrieveSync() - .toEntity(Project.class); +include::{include-java}/client/requests/documentsource/DocumentSource.java[tag=documentSource,indent=0] ---- <1> Load the document from "projectReleases.graphql" <2> Provide variable values. -This approach also works for loading fragments for your queries. -Fragments are reusable field selection sets that avoid repetition in a request document. -For example, we can use a `...releases` fragment in multiple queries: - -[source,graphql,indent=0,subs="verbatim,quotes"] -.src/main/resources/graphql-documents/projectReleases.graphql ----- - query frameworkReleases { - project(slug: "spring-framework") { - name - ...releases - } - } - query graphqlReleases { - project(slug: "spring-graphql") { - name - ...releases - } - } ----- - -This fragment can be defined in a separate file for reuse: - -[source,graphql,indent=0,subs="verbatim,quotes"] -.src/main/resources/graphql-documents/releases.graphql ----- - fragment releases on Project { - releases { - version - } - } ----- - - -You can then send this fragment along the query document: - -[source,java,indent=0,subs="verbatim,quotes"] ----- - Project project = graphQlClient.documentName("projectReleases") <1> - .fragmentName("releases") <2> - .retrieveSync() - .toEntity(Project.class); ----- -<1> Load the document from "projectReleases.graphql" -<2> Load the fragment from "releases.graphql" and append it to the document - - - The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion. You can use the `GraphQlClient` xref:client.adoc#client.graphqlclient.builder[Builder] to customize the @@ -552,18 +345,13 @@ To start a subscription stream, use `retrieveSubscription` which is similar to xref:client.adoc#client.requests.retrieve[retrieve] for a single response but returning a stream of responses, each decoded to some data: -[source,java,indent=0,subs="verbatim,quotes"] ----- - Flux greetingFlux = client.document("subscription { greetings }") - .retrieveSubscription("greeting") - .toEntity(String.class); ----- +include-code::RetrieveSubscription[tag=subscriptionRetrieve,indent=0] The `Flux` may terminate with `SubscriptionErrorException` if the subscription ends from the server side with an "error" message. The exception provides access to GraphQL errors decoded from the "error" message. -The `Flux` may termiate with `GraphQlTransportException` such as +The `Flux` may terminate with `GraphQlTransportException` such as `WebSocketDisconnectedException` if the underlying connection is closed or lost. In that case you can use the `retry` operator to restart the subscription. @@ -582,29 +370,7 @@ xref:client.adoc#client.subscriptions.retrieve[Retrieve] is only a shortcut to d response map. For more control, use the `executeSubscription` method and handle each response directly: -[source,java,indent=0,subs="verbatim,quotes"] ----- - Flux greetingFlux = client.document("subscription { greetings }") - .executeSubscription() - .map(response -> { - if (!response.isValid()) { - // Request failure... - } - - ClientResponseField field = response.field("project"); - if (!field.hasValue()) { - if (field.getError() != null) { - // Field failure... - } - else { - // Optional field set to null... <3> - } - } - - return field.toEntity(String.class) - }); ----- - +include-code::ExecuteSubscription[tag=subscriptionExecute,indent=0] @@ -614,52 +380,16 @@ response directly: For blocking transports created with the `GraphQlClient.SyncBuilder`, you create a `SyncGraphQlClientInterceptor` to intercept all requests through the client: -[source,java,indent=0,subs="verbatim,quotes"] ----- -static class MyInterceptor implements SyncGraphQlClientInterceptor { - - @Override - public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) { - // ... - return chain.next(request); - } -} ----- +include-code::SyncInterceptor[] For non-blocking transports created with `GraphQlClient.Builder`, you create a `GraphQlClientInterceptor` to intercept all requests through the client: -[source,java,indent=0,subs="verbatim,quotes"] ----- -static class MyInterceptor implements GraphQlClientInterceptor { - - @Override - public Mono intercept(ClientGraphQlRequest request, Chain chain) { - // ... - return chain.next(request); - } - - @Override - public Flux interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) { - // ... - return chain.next(request); - } - -} ----- +include-code::MyInterceptor[] Once the interceptor is created, register it through the client builder. For example: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI url = ... ; - WebSocketClient client = ... ; - - WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) - .interceptor(new MyInterceptor()) - .build(); ----- - +include-code::UseInterceptor[tag=register,indent=0] @@ -697,7 +427,7 @@ You can perform a request as follows: List books = dgsClient.request(new BooksGraphQLQuery()) // <2> .projection(new BooksProjectionRoot<>().id().name()) // <3> - .retrieveSync() + .retrieveSync("books") .toEntityList(Book.class); ---- diff --git a/spring-graphql-docs/modules/ROOT/pages/testing.adoc b/spring-graphql-docs/modules/ROOT/pages/testing.adoc index 1793d926..34f77cb0 100644 --- a/spring-graphql-docs/modules/ROOT/pages/testing.adoc +++ b/spring-graphql-docs/modules/ROOT/pages/testing.adoc @@ -74,66 +74,22 @@ GraphQL requests over HTTP, with or without a live server, depending on how 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); ----- +include-code::HttpSetup[tag=webTestClient,indent=0] 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); ----- +include-code::HttpSetup[tag=mockMvc,indent=0] 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); ----- +include-code::HttpSetup[tag=liveServer,indent=0] Once `HttpGraphQlTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying transport. If you need to change any transport specific details, use `mutate()` on an existing `HttpSocketGraphQlTester` to create a new instance with customized settings: -[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... - ----- +include-code::HttpSetup[tag=executeRequests,indent=0] @@ -145,13 +101,7 @@ It is built using the {spring-framework-ref-docs}/web/webflux-websocket.html#webflux-websocket-client[WebSocketClient] from Spring WebFlux and you can create it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - String url = "http://localhost:8080/graphql"; - WebSocketClient client = new ReactorNettyWebSocketClient(); - - WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build(); ----- +include-code::WsSetup[tag=setup,indent=0] `WebSocketGraphQlTester` is connection oriented and multiplexed. Each instance establishes its own single, shared connection for all requests. Typically, you'll want to use a single @@ -162,24 +112,7 @@ xref:testing.adoc#testing.requests[execute requests] using the same API, indepen transport. If you need to change any transport specific details, use `mutate()` on an existing `WebSocketGraphQlTester` to create a new instance with customized settings: - -[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... ----- +include-code::WsSetup[tag=customSetup,indent=0] `WebSocketGraphQlTester` provides a `stop()` method that you can use to have the WebSocket connection closed, e.g. after a test runs. @@ -192,15 +125,7 @@ connection closed, e.g. after a test runs. `RSocketGraphQlTester` uses `RSocketRequester` from spring-messaging to execute GraphQL requests over RSocket: -[source,java,indent=0,subs="verbatim,quotes"] ----- - URI uri = URI.create("wss://localhost:8080/rsocket"); - WebsocketClientTransport transport = WebsocketClientTransport.create(url); - - RSocketGraphQlTester client = RSocketGraphQlTester.builder() - .clientTransport(transport) - .build(); ----- +include-code::RSocketSetup[tag=rsocketSetup,indent=0] `RSocketGraphQlTester` is connection oriented and multiplexed. Each instance establishes its own single, shared session for all requests. Typically, you'll want to use a single @@ -219,11 +144,7 @@ Many times it's enough to test GraphQL requests on the server side, without the 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"] ----- - ExecutionGraphQlService service = ... ; - ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service); ----- +include-code::ServiceSetup[tag=serviceSetup,indent=0] Once `ExecutionGraphQlServiceTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying @@ -231,13 +152,7 @@ transport. `ExecutionGraphQlServiceTester.Builder` provides an option to customize `ExecutionInput` details: -[source,java,indent=0,subs="verbatim,quotes"] ----- - ExecutionGraphQlService service = ... ; - ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service) - .configureExecutionInput((executionInput, builder) -> builder.executionId(id).build()) - .build(); ----- +include-code::ServiceSetup[tag=customServiceSetup,indent=0] @@ -252,22 +167,11 @@ The `WebGraphQlTester` 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 = ... ; - WebGraphQlTester tester = WebGraphQlTester.create(handler); ----- +include-code::WebSetup[tag=webSetup,indent=0] The builder for this extension allows you to define HTTP request details: -[source,java,indent=0,subs="verbatim,quotes"] ----- - WebGraphQlHandler handler = ... ; - - WebGraphQlTester tester = WebGraphQlTester.builder(handler) - .headers(headers -> headers.setBasicAuth("joe", "...")) - .build(); ----- +include-code::WebSetup[tag=customWebSetup,indent=0] Once `WebGraphQlTester` is created, you can begin to xref:testing.adoc#testing.requests[execute requests] using the same API, independent of the underlying transport. @@ -297,22 +201,7 @@ Once you have a `GraphQlTester`, you can begin to test requests. The below execu 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); ----- +include-code::TesterRequests[tag=inlineDocument,indent=0] The JsonPath is relative to the "data" section of the response. @@ -335,18 +224,52 @@ For example, given a file called `projectReleases.graphql` in 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); ----- +include-code::TesterRequests[tag=documentName,indent=0] <1> Refer to the document in the file named "project". <2> Set the `slug` variable. + +This approach also works for loading fragments for your queries. +Fragments are reusable field selection sets that avoid repetition in a request document. +For example, we can use a `...releases` fragment in multiple queries: + +[source,graphql,indent=0,subs="verbatim,quotes"] +.src/main/resources/graphql-documents/projectReleases.graphql +---- + query frameworkReleases { + project(slug: "spring-framework") { + name + ...releases + } + } + query graphqlReleases { + project(slug: "spring-graphql") { + name + ...releases + } + } +---- + +This fragment can be defined in a separate file for reuse: + +[source,graphql,indent=0,subs="verbatim,quotes"] +.src/main/resources/graphql-documents/releases.graphql +---- + fragment releases on Project { + releases { + version + } + } +---- + + +You can then send this fragment along the query document: + +include-code::TesterRequests[tag=fragment,indent=0] +<1> Load the document from "projectReleases.graphql" +<2> Load the fragment from "releases.graphql" and append it to the document + + [TIP] ==== The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion. @@ -364,45 +287,24 @@ See xref:testing.adoc#testing.errors[Errors] for more details on error handling. -[[testing.requests.nestedPaths]] +[[testing.requests.nestedpaths]] === Nested Paths By default, paths are relative to the "data" section of the GraphQL response. You can also nest down to a path, and inspect multiple paths relative to it as follows: -[source,java,indent=0,subs="verbatim,quotes"] ----- - graphQlTester.document(document) - .execute() - .path("project", project -> project // <1> - .path("name").entity(String.class).isEqualTo("spring-framework") - .path("releases[*].version").entityList(String.class).hasSizeGreaterThan(1)); ----- - +include-code::NestedPaths[tag=nestedPaths,indent=0] <1> Use a callback to inspect paths relative to "project". - - [[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 greetingFlux = tester.document("subscription { greetings }") - .executeSubscription() - .toFlux("greetings", String.class); // decode at JSONPath - - StepVerifier.create(greetingFlux) - .expectNext("Hi") - .expectNext("Bonjour") - .expectNext("Hola") - .verifyComplete(); ----- +include-code::TestSubscriptions[tag=testSubscriptions,indent=0] Subscriptions are supported only with xref:testing.adoc#testing.websocketgraphqltester[WebSocketGraphQlTester] , or with the server side @@ -417,51 +319,19 @@ When you use `verify()`, any errors under the "errors" key in the response will 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); ----- + +include-code::TestErrors[tag=verifyErrors,indent=0] 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(); ----- +include-code::TestErrors[tag=setupErrorFilter,indent=0] 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 `expect` 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); ----- +include-code::TestErrors[tag=expectedErrors,indent=0] 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 -> { - // ... - }); ----- +include-code::TestErrors[tag=satisfyErrors,indent=0] diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java new file mode 100644 index 00000000..73502e07 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpgraphqlclient/ClientUsage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.httpgraphqlclient; + +import org.springframework.graphql.client.HttpGraphQlClient; +import org.springframework.web.reactive.function.client.WebClient; + +public class ClientUsage { + + void create() { + // tag::create[] + WebClient webClient = WebClient.create("https://spring.io/graphql"); + HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient); + // end::create[] + } + + void mutateClient() { + // tag::mutate[] + WebClient webClient = WebClient.create("https://spring.io/graphql"); + + 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... + // end::mutate[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java new file mode 100644 index 00000000..4a5f5e6b --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/httpsyncgraphqlclient/SyncClientUsage.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.httpsyncgraphqlclient; + +import org.springframework.graphql.client.HttpSyncGraphQlClient; +import org.springframework.web.client.RestClient; + +public class SyncClientUsage { + + void createClient() { + // tag::create[] + RestClient restClient = RestClient.create("https://spring.io/graphql"); + HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient); + // end::create[] + } + + void mutateClient() { + // tag::mutate[] + RestClient restClient = RestClient.create("https://spring.io/graphql"); + HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + + // Perform requests with graphQlClient... + + HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate() + .headers((headers) -> headers.setBasicAuth("peter", "...")) + .build(); + + // Perform requests with anotherGraphQlClient... + + // end::mutate[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java new file mode 100644 index 00000000..3c509c4d --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/MyInterceptor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.interception; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlRequest; +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.GraphQlClientInterceptor; + +public class MyInterceptor implements GraphQlClientInterceptor { + + @Override + public Mono intercept(ClientGraphQlRequest request, Chain chain) { + // ... + return chain.next(request); + } + + @Override + public Flux interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) { + // ... + return chain.next(request); + } + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java new file mode 100644 index 00000000..f26a72ba --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/SyncInterceptor.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.interception; + +import org.springframework.graphql.client.ClientGraphQlRequest; +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.SyncGraphQlClientInterceptor; + +public class SyncInterceptor implements SyncGraphQlClientInterceptor { + + @Override + public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) { + // ... + return chain.next(request); + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java new file mode 100644 index 00000000..f7c592fc --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/interception/UseInterceptor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.interception; + +import java.net.URI; + +import org.springframework.graphql.client.WebSocketGraphQlClient; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class UseInterceptor { + + void registerInterceptor() { + // tag::register[] + URI url = URI.create("wss://localhost:8080/graphql"); + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .interceptor(new MyInterceptor()) + .build(); + // end::register[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java new file mode 100644 index 00000000..aa15ef66 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/documentsource/DocumentSource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.requests.documentsource; + +import org.springframework.graphql.client.GraphQlClient; + +public class DocumentSource { + + void documentSource() { + GraphQlClient graphQlClient = null; + // tag::documentSource[] + Project project = graphQlClient.documentName("projectReleases") // <1> + .variable("slug", "spring-framework") // <2> + .retrieveSync("projectReleases.project") + .toEntity(Project.class); + // end::documentSource[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java new file mode 100644 index 00000000..beb5f821 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/execute/Execute.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.requests.execute; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.GraphQlClient; + +public class Execute { + + void executeSync() { + GraphQlClient graphQlClient = null; + String document = ""; + // tag::executeSync[] + ClientGraphQlResponse response = graphQlClient.document(document).executeSync(); + + if (!response.isValid()) { + // Request failure... <1> + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... <2> + } + else { + // Field failure... <3> + } + } + + Project project = field.toEntity(Project.class); // <4> + // end::executeSync[] + } + + void execute() { + GraphQlClient graphQlClient = null; + String document = ""; + // tag::execute[] + Mono projectMono = graphQlClient.document(document) + .execute() + .map((response) -> { + if (!response.isValid()) { + // Request failure... <1> + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... <2> + } + else { + // Field failure... <3> + } + } + + return field.toEntity(Project.class); // <4> + }); + // end::execute[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java new file mode 100644 index 00000000..0551a167 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/requests/retrieve/Retrieve.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.requests.retrieve; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.client.ClientGraphQlResponse; +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.FieldAccessException; +import org.springframework.graphql.client.GraphQlClient; + +public class Retrieve { + + void retrieveSync() { + GraphQlClient graphQlClient = null; + + // tag::retrieveSync[] + String document = + """ + { + project(slug:"spring-framework") { + name + releases { + version + } + } + } + """; + + Project project = graphQlClient.document(document) // <1> + .retrieveSync("project") // <2> + .toEntity(Project.class); // <3> + // end::retrieveSync[] + } + + void retrieve() { + GraphQlClient graphQlClient = null; + + // tag::retrieve[] + String document = + """ + { + project(slug:"spring-framework") { + name + releases { + version + } + } + } + """; + + Mono project = graphQlClient.document(document) // <1> + .retrieve("project") // <2> + .toEntity(Project.class); // <3> + // end::retrieve[] + } + + Project fieldErrorSync() { + GraphQlClient graphQlClient = null; + String document = ""; + + // tag::fieldErrorSync[] + try { + Project project = graphQlClient.document(document) + .retrieveSync("project") + .toEntity(Project.class); + return project; + } + catch (FieldAccessException ex) { + ClientGraphQlResponse response = ex.getResponse(); + // ... + ClientResponseField field = ex.getField(); + // return fallback value + return new Project(); + } + // end::fieldErrorSync[] + } + + void fieldError() { + GraphQlClient graphQlClient = null; + String document = ""; + + // tag::fieldError[] + Mono projectMono = graphQlClient.document(document) + .retrieve("project") + .toEntity(Project.class) + .onErrorResume(FieldAccessException.class, (ex) -> { + ClientGraphQlResponse response = ex.getResponse(); + // ... + ClientResponseField field = ex.getField(); + // return fallback value + return Mono.just(new Project()); + }); + // end::fieldError[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java new file mode 100644 index 00000000..a8542d82 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/rsocketgraphqlclient/RSocketClientUsage.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.rsocketgraphqlclient; + +import java.net.URI; + +import io.rsocket.transport.netty.client.WebsocketClientTransport; + +import org.springframework.graphql.client.RSocketGraphQlClient; + +public class RSocketClientUsage { + + void create() { + // tag::create[] + URI uri = URI.create("wss://localhost:8080/rsocket"); + WebsocketClientTransport transport = WebsocketClientTransport.create(uri); + + RSocketGraphQlClient client = RSocketGraphQlClient.builder() + .clientTransport(transport) + .build(); + // end::create[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java new file mode 100644 index 00000000..ffa22978 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/execute/ExecuteSubscription.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.subscriptions.execute; + +import reactor.core.publisher.Flux; + +import org.springframework.graphql.client.ClientResponseField; +import org.springframework.graphql.client.GraphQlClient; + +public class ExecuteSubscription { + + void subscriptionExecute() { + GraphQlClient client = null; + // tag::subscriptionExecute[] + Flux greetingFlux = client.document("subscription { greetings }") + .executeSubscription() + .map((response) -> { + if (!response.isValid()) { + // Request failure... + } + + ClientResponseField field = response.field("project"); + if (field.getValue() == null) { + if (field.getErrors().isEmpty()) { + // Optional field set to null... + } + else { + // Field failure... + } + } + + return field.toEntity(String.class); + }); + // end::subscriptionExecute[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java new file mode 100644 index 00000000..b0c58148 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/subscriptions/retrieve/RetrieveSubscription.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.subscriptions.retrieve; + +import reactor.core.publisher.Flux; + +import org.springframework.graphql.client.GraphQlClient; + +public class RetrieveSubscription { + + void subscriptionRetrieve() { + GraphQlClient client = null; + // tag::subscriptionRetrieve[] + Flux greetingFlux = client.document("subscription { greetings }") + .retrieveSubscription("greeting") + .toEntity(String.class); + // end::subscriptionRetrieve[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java new file mode 100644 index 00000000..72ca3a9b --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/client/websocketgraphqlclient/WebSocketClientUsage.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.client.websocketgraphqlclient; + +import java.time.Duration; + +import org.springframework.graphql.client.WebSocketGraphQlClient; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class WebSocketClientUsage { + + void create() { + // tag::create[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build(); + // end::create[] + } + + void mutate() { + // tag::mutate[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + 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... + + // end::mutate[] + } + + void keepAlive() { + // tag::keepAlive[] + String url = "wss://spring.io/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .keepAlive(Duration.ofSeconds(30)) + .build(); + // end::keepAlive[] + } + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java new file mode 100644 index 00000000..9cad4f86 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/errors/TestErrors.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.errors; + +import org.springframework.graphql.server.WebGraphQlHandler; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestErrors { + + void verifyErrors() { + GraphQlTester graphQlTester = null; + String query = ""; + // tag::verifyErrors[] + graphQlTester.document(query) + .execute() + .errors() + .filter((error) -> error.getMessage().equals("ignored error")) + .verify() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::verifyErrors[] + } + + void setupErrorFilter() { + WebGraphQlHandler handler = null; + // tag::setupErrorFilter[] + WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler) + .errorFilter((error) -> error.getMessage().equals("ignored error")) + .build(); + // end::setupErrorFilter[] + } + + void expectedErrors() { + GraphQlTester graphQlTester = null; + String query = ""; + // tag::expectedErrors[] + graphQlTester.document(query) + .execute() + .errors() + .expect((error) -> error.getMessage().equals("expected error")) + .verify() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::expectedErrors[] + } + + void satisfyErrors() { + GraphQlTester graphQlTester = null; + String document = ""; + // tag::satisfyErrors[] + graphQlTester.document(document) + .execute() + .errors() + .satisfy((errors) -> + assertThat(errors) + .anyMatch((error) -> error.getMessage().contains("ignored error")) + ); + // end::satisfyErrors[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java new file mode 100644 index 00000000..efceb298 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/graphqlservicetester/ServiceSetup.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.graphqlservicetester; + +import graphql.execution.ExecutionId; + +import org.springframework.graphql.ExecutionGraphQlService; +import org.springframework.graphql.test.tester.ExecutionGraphQlServiceTester; + +public class ServiceSetup { + + void serviceSetup() { + // tag::serviceSetup[] + ExecutionGraphQlService service = /**/ null; + ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service); + // end::serviceSetup[] + } + + void customServiceSetup() { + // tag::customServiceSetup[] + ExecutionGraphQlService service = /**/ null; + ExecutionId executionId = ExecutionId.generate(); + ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.builder(service) + .configureExecutionInput((executionInput, builder) -> builder.executionId(executionId).build()) + .build(); + // end::customServiceSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java new file mode 100644 index 00000000..04f3df6a --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/httpgraphqltester/HttpSetup.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.httpgraphqltester; + +import org.springframework.graphql.test.tester.HttpGraphQlTester; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.servlet.client.MockMvcWebTestClient; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +public class HttpSetup { + + void webTestClient() { + // tag::webTestClient[] + AnnotationConfigWebApplicationContext context = /**/ new AnnotationConfigWebApplicationContext(); + + WebTestClient client = + WebTestClient.bindToApplicationContext(context) + .configureClient() + .baseUrl("/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::webTestClient[] + } + + void mockMvc() { + // tag::mockMvc[] + AnnotationConfigWebApplicationContext context = /**/ new AnnotationConfigWebApplicationContext(); + + WebTestClient client = + MockMvcWebTestClient.bindToApplicationContext(context) + .configureClient() + .baseUrl("/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::mockMvc[] + } + + void liveServer() { + // tag::liveServer[] + WebTestClient client = + WebTestClient.bindToServer() + .baseUrl("http://localhost:8080/graphql") + .build(); + + HttpGraphQlTester tester = HttpGraphQlTester.create(client); + // end::liveServer[] + } + + void executeRequests() { + // tag::executeRequests[] + WebTestClient.Builder clientBuilder = + WebTestClient.bindToServer() + .baseUrl("http://localhost:8080/graphql"); + + 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... + // end::executeRequests[] + } + + static class Configuration { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java new file mode 100644 index 00000000..eebff4ed --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/TesterRequests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.requests; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class TesterRequests { + + void inlineDocument() { + GraphQlTester graphQlTester = null; + // tag::inlineDocument[] + String document = + """ + { + project(slug:"spring-framework") { + releases { + version + } + } + } + """; + + graphQlTester.document(document) + .execute() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::inlineDocument[] + } + + void documentName() { + GraphQlTester graphQlTester = null; + // tag::documentName[] + graphQlTester.documentName("projectReleases") // <1> + .variable("slug", "spring-framework") // <2> + .execute() + .path("projectReleases.project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::documentName[] + } + + void fragment() { + GraphQlTester graphQlTester = null; + // tag::fragment[] + graphQlTester.documentName("projectReleases") // <1> + .fragmentName("releases") // <2> + .execute() + .path("frameworkReleases.project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + // end::fragment[] + } + + record Project() { + + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java new file mode 100644 index 00000000..a95fba58 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/requests/nestedpaths/NestedPaths.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.requests.nestedpaths; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class NestedPaths { + + void nestedPaths() { + GraphQlTester graphQlTester = null; + String document = ""; + // tag::nestedPaths[] + graphQlTester.document(document) + .execute() + .path("project", (project) -> project // <1> + .path("name").entity(String.class).isEqualTo("spring-framework") + .path("releases[*].version").entityList(String.class).hasSizeGreaterThan(1)); + // end::nestedPaths[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java new file mode 100644 index 00000000..79b09e28 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/rsocketgraphqltester/RSocketSetup.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.rsocketgraphqltester; + +import java.net.URI; + +import io.rsocket.transport.netty.client.WebsocketClientTransport; + +import org.springframework.graphql.test.tester.RSocketGraphQlTester; + +public class RSocketSetup { + + void rSocketSetup() { + // tag::rsocketSetup[] + URI url = URI.create("wss://localhost:8080/rsocket"); + WebsocketClientTransport transport = WebsocketClientTransport.create(url); + + RSocketGraphQlTester client = RSocketGraphQlTester.builder() + .clientTransport(transport) + .build(); + // end::rsocketSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java new file mode 100644 index 00000000..051a08ca --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/subscriptions/TestSubscriptions.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.subscriptions; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +import org.springframework.graphql.test.tester.GraphQlTester; + +public class TestSubscriptions { + + void testSubscriptions() { + GraphQlTester tester = null; + // tag::testSubscriptions[] + Flux greetingFlux = tester.document("subscription { greetings }") + .executeSubscription() + .toFlux("greetings", String.class); // decode at JSONPath + + StepVerifier.create(greetingFlux) + .expectNext("Hi") + .expectNext("Bonjour") + .expectNext("Hola") + .verifyComplete(); + // end::testSubscriptions[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java new file mode 100644 index 00000000..1081e899 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/webgraphqltester/WebSetup.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.webgraphqltester; + +import org.springframework.graphql.server.WebGraphQlHandler; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +public class WebSetup { + + void webSetup() { + // tag::webSetup[] + WebGraphQlHandler handler = /**/ null; + WebGraphQlTester tester = WebGraphQlTester.create(handler); + // end::webSetup[] + } + + void customWebSetup() { + // tag::customWebSetup[] + WebGraphQlHandler handler = /**/ null; + WebGraphQlTester tester = WebGraphQlTester.builder(handler) + .headers((headers) -> headers.setBasicAuth("joe", "...")) + .build(); + // end::customWebSetup[] + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java new file mode 100644 index 00000000..0405a901 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/testing/websocketgraphqltester/WsSetup.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.docs.testing.websocketgraphqltester; + +import java.net.URI; + +import org.springframework.graphql.test.tester.WebSocketGraphQlTester; +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; +import org.springframework.web.reactive.socket.client.WebSocketClient; + +public class WsSetup { + + void setup() { + // tag::setup[] + String url = "http://localhost:8080/graphql"; + WebSocketClient client = new ReactorNettyWebSocketClient(); + + WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build(); + // end::setup[] + } + + void customSetup() { + // tag::customSetup[] + URI url = URI.create("ws://localhost:8080/graphql"); + WebSocketClient client = new ReactorNettyWebSocketClient(); + + 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... + // end::customSetup[] + } +}