Decouple GraphQlTester from Web details

Closes gh-65
This commit is contained in:
Rossen Stoyanchev
2021-06-14 14:21:02 +01:00
parent 17bf9ee287
commit de234c94c4
10 changed files with 589 additions and 383 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.boot.GraphQlProperties;
import org.springframework.graphql.test.tester.GraphQlTester;
import org.springframework.graphql.test.tester.WebGraphQlTester;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
@@ -45,9 +46,9 @@ public class GraphQlTesterAutoConfiguration {
public static class WebTestClientGraphQlTesterConfiguration {
@Bean
public GraphQlTester clientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) {
public WebGraphQlTester clientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) {
WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build();
return GraphQlTester.create(mutatedWebTestClient);
return WebGraphQlTester.create(mutatedWebTestClient);
}
}
@@ -58,8 +59,8 @@ public class GraphQlTesterAutoConfiguration {
public static class WebGraphQlHandlerGraphQlTesterConfiguration {
@Bean
public GraphQlTester handlerGraphQlTester(WebGraphQlHandler handler) {
return GraphQlTester.create(handler);
public WebGraphQlTester handlerGraphQlTester(WebGraphQlHandler handler) {
return WebGraphQlTester.create(handler);
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.graphql.test.tester.GraphQlTester;
import org.springframework.graphql.test.tester.WebGraphQlTester;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
@@ -142,12 +143,12 @@ class GraphQlTesterContextCustomizer implements ContextCustomizer {
return this.object;
}
private GraphQlTester createGraphQlTester() {
private WebGraphQlTester createGraphQlTester() {
WebTestClient webTestClient = this.applicationContext.getBean(WebTestClient.class);
boolean sslEnabled = isSslEnabled(this.applicationContext);
String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080");
WebTestClient mutatedWebClient = webTestClient.mutate().baseUrl(getBaseUrl(sslEnabled, port)).build();
return GraphQlTester.create(mutatedWebClient);
return WebGraphQlTester.create(mutatedWebClient);
}
private String getBaseUrl(boolean sslEnabled, String port) {

View File

@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.GraphQlTester;
import org.springframework.graphql.test.tester.WebGraphQlTester;
import org.springframework.graphql.web.WebGraphQlHandler;
// @formatter:off
@@ -36,7 +37,7 @@ public class QueryTests {
@BeforeEach
public void setUp(@Autowired WebGraphQlHandler handler) {
this.graphQlTester = GraphQlTester.create(webInput ->
this.graphQlTester = WebGraphQlTester.create(webInput ->
handler.handle(webInput).contextWrite(context -> context.put("name", "James")));
}

View File

@@ -24,6 +24,7 @@ import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.GraphQlTester;
import org.springframework.graphql.test.tester.WebGraphQlTester;
import org.springframework.graphql.web.WebGraphQlHandler;
// @formatter:off
@@ -38,7 +39,7 @@ public class SubscriptionTests {
@BeforeEach
public void setUp(@Autowired WebGraphQlHandler handler) {
this.graphQlTester = GraphQlTester.create(webInput ->
this.graphQlTester = WebGraphQlTester.create(webInput ->
handler.handle(webInput).contextWrite(context -> context.put("name", "James")));
}

View File

@@ -16,8 +16,6 @@
package org.springframework.graphql.test.tester;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,24 +35,17 @@ import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.test.util.AssertionErrors;
import org.springframework.test.util.JsonExpectationsHelper;
import org.springframework.test.util.JsonPathExpectationsHelper;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
@@ -77,19 +68,16 @@ class DefaultGraphQlTester implements GraphQlTester {
private final RequestStrategy requestStrategy;
private final Configuration jsonPathConfig;
DefaultGraphQlTester(WebTestClient client) {
this.jsonPathConfig = initJsonPathConfig();
this.requestStrategy = new WebTestClientRequestStrategy(client, this.jsonPathConfig);
DefaultGraphQlTester(GraphQlService service) {
this(new GraphQlServiceRequestStrategy(service, initJsonPathConfig()));
}
DefaultGraphQlTester(WebGraphQlHandler handler) {
this.jsonPathConfig = initJsonPathConfig();
this.requestStrategy = new DirectRequestStrategy(handler, this.jsonPathConfig);
DefaultGraphQlTester(RequestStrategy requestStrategy) {
this.requestStrategy = requestStrategy;
}
private Configuration initJsonPathConfig() {
protected static Configuration initJsonPathConfig() {
return (jackson2Present ? Jackson2Configuration.create() : Configuration.builder().build());
}
@@ -120,65 +108,17 @@ class DefaultGraphQlTester implements GraphQlTester {
}
/**
* {@link RequestStrategy} that works as an HTTP client with requests executed through
* {@link WebTestClient} that in turn may work connect with or without a live server
* for Spring MVC and WebFlux.
* Base class for a {@link RequestStrategy} that perform GraphQL requests
* without an underlying transport and where {@link RequestInput} provides
* sufficient input.
*/
private static class WebTestClientRequestStrategy implements RequestStrategy {
protected abstract static class AbstractDirectRequestStrategy implements RequestStrategy {
private final WebTestClient client;
protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5);
private final Configuration jsonPathConfig;
WebTestClientRequestStrategy(WebTestClient client, Configuration jsonPathConfig) {
this.client = client;
this.jsonPathConfig = jsonPathConfig;
}
@Override
public ResponseSpec execute(RequestInput requestInput) {
EntityExchangeResult<byte[]> result = this.client.post().contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestInput).exchange().expectStatus().isOk().expectHeader()
.contentType(MediaType.APPLICATION_JSON).expectBody().returnResult();
byte[] bytes = result.getResponseBodyContent();
Assert.notNull(bytes, "Expected GraphQL response content");
String content = new String(bytes, StandardCharsets.UTF_8);
DocumentContext documentContext = JsonPath.parse(content, this.jsonPathConfig);
return new DefaultResponseSpec(documentContext, result::assertWithDiagnostics);
}
@Override
public SubscriptionSpec executeSubscription(RequestInput requestInput) {
FluxExchangeResult<TestExecutionResult> exchangeResult = this.client.post()
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.TEXT_EVENT_STREAM).bodyValue(requestInput)
.exchange().expectStatus().isOk().expectHeader().contentType(MediaType.TEXT_EVENT_STREAM)
.returnResult(TestExecutionResult.class);
return new DefaultSubscriptionSpec(exchangeResult.getResponseBody().cast(ExecutionResult.class),
this.jsonPathConfig, exchangeResult::assertWithDiagnostics);
}
}
/**
* {@link RequestStrategy} that performs requests directly on {@link GraphQL}.
*/
private static class DirectRequestStrategy implements RequestStrategy {
private static final URI DEFAULT_URL = URI.create("http://localhost:8080/graphql");
private static final HttpHeaders DEFAULT_HEADERS = new HttpHeaders();
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5);
private final WebGraphQlHandler graphQlHandler;
private final Configuration jsonPathConfig;
DirectRequestStrategy(WebGraphQlHandler handler, Configuration jsonPathConfig) {
this.graphQlHandler = handler;
protected AbstractDirectRequestStrategy(Configuration jsonPathConfig) {
this.jsonPathConfig = jsonPathConfig;
}
@@ -196,19 +136,16 @@ class DefaultGraphQlTester implements GraphQlTester {
List<GraphQLError> errors = result.getErrors();
Consumer<Runnable> assertDecorator = assertDecorator(input);
assertDecorator
.accept(() -> AssertionErrors.assertTrue("Response has " + errors.size() + " unexpected error(s).",
CollectionUtils.isEmpty(errors)));
assertDecorator.accept(() -> AssertionErrors.assertTrue(
"Response has " + errors.size() + " unexpected error(s).", CollectionUtils.isEmpty(errors)));
return new DefaultSubscriptionSpec(result.getData(), this.jsonPathConfig, assertDecorator);
}
private ExecutionResult executeInternal(RequestInput input) {
WebInput webInput = new WebInput(DEFAULT_URL, DEFAULT_HEADERS, input.toMap(), null);
ExecutionResult result = this.graphQlHandler.handle(webInput).block(DEFAULT_TIMEOUT);
Assert.notNull(result, "Expected ExecutionResult");
return result;
}
/**
* Sub-classes implement this to actual perform the request.
*/
protected abstract ExecutionResult executeInternal(RequestInput input);
private Consumer<Runnable> assertDecorator(RequestInput input) {
return (assertion) -> {
@@ -223,6 +160,25 @@ class DefaultGraphQlTester implements GraphQlTester {
}
/**
* {@link RequestStrategy} that performs requests through a {@link GraphQlService}.
*/
protected static class GraphQlServiceRequestStrategy extends AbstractDirectRequestStrategy {
private final GraphQlService graphQlService;
protected GraphQlServiceRequestStrategy(GraphQlService service, Configuration jsonPathConfig) {
super(jsonPathConfig);
this.graphQlService = service;
}
protected ExecutionResult executeInternal(RequestInput input) {
ExecutionResult result = this.graphQlService.execute(input.toExecutionInput()).block(DEFAULT_TIMEOUT);
Assert.notNull(result, "Expected ExecutionResult");
return result;
}
}
/**
* {@link RequestSpec} that collects the query, operationName, and variables.
*/
@@ -375,7 +331,7 @@ class DefaultGraphQlTester implements GraphQlTester {
/**
* {@link ResponseSpec} that operates on the response from a GraphQL HTTP request.
*/
private static final class DefaultResponseSpec implements ResponseSpec, ErrorSpec {
protected static final class DefaultResponseSpec implements ResponseSpec, ErrorSpec {
private final ResponseContainer responseContainer;
@@ -385,7 +341,7 @@ class DefaultGraphQlTester implements GraphQlTester {
* @param assertDecorator decorator to apply around assertions, e.g. to add extra
* contextual information such as HTTP request and response body details
*/
private DefaultResponseSpec(DocumentContext documentContext, Consumer<Runnable> assertDecorator) {
protected DefaultResponseSpec(DocumentContext documentContext, Consumer<Runnable> assertDecorator) {
this.responseContainer = new ResponseContainer(documentContext, assertDecorator);
}
@@ -714,7 +670,7 @@ class DefaultGraphQlTester implements GraphQlTester {
* {@link SubscriptionSpec} implementation that operates on a {@link Publisher} of
* {@link ExecutionResult}.
*/
private static class DefaultSubscriptionSpec implements SubscriptionSpec {
protected static class DefaultSubscriptionSpec implements SubscriptionSpec {
private final Publisher<ExecutionResult> publisher;
@@ -722,7 +678,7 @@ class DefaultGraphQlTester implements GraphQlTester {
private final Consumer<Runnable> assertDecorator;
<T> DefaultSubscriptionSpec(Publisher<ExecutionResult> publisher, Configuration jsonPathConfig,
protected <T> DefaultSubscriptionSpec(Publisher<ExecutionResult> publisher, Configuration jsonPathConfig,
Consumer<Runnable> decorator) {
this.publisher = publisher;

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-2021 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.test.tester;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import graphql.ExecutionResult;
import org.springframework.graphql.RequestInput;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.Assert;
/**
* Default implementation of {@link WebGraphQlTester}.
*
* @author Rossen Stoyanchev
*/
class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQlTester {
DefaultWebGraphQlTester(WebTestClient client) {
super(new WebTestClientRequestStrategy(client, initJsonPathConfig()));
}
DefaultWebGraphQlTester(WebGraphQlHandler handler) {
super(new WebGraphQlHandlerRequestStrategy(handler, initJsonPathConfig()));
}
/**
* {@link RequestStrategy} that works as an HTTP client with requests executed through
* {@link WebTestClient} that in turn may work connect with or without a live server
* for Spring MVC and WebFlux.
*/
private static class WebTestClientRequestStrategy implements RequestStrategy {
private final WebTestClient client;
private final Configuration jsonPathConfig;
WebTestClientRequestStrategy(WebTestClient client, Configuration jsonPathConfig) {
this.client = client;
this.jsonPathConfig = jsonPathConfig;
}
@Override
public ResponseSpec execute(RequestInput requestInput) {
EntityExchangeResult<byte[]> result = this.client.post().contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestInput).exchange().expectStatus().isOk().expectHeader()
.contentType(MediaType.APPLICATION_JSON).expectBody().returnResult();
byte[] bytes = result.getResponseBodyContent();
Assert.notNull(bytes, "Expected GraphQL response content");
String content = new String(bytes, StandardCharsets.UTF_8);
DocumentContext documentContext = JsonPath.parse(content, this.jsonPathConfig);
return new DefaultResponseSpec(documentContext, result::assertWithDiagnostics);
}
@Override
public SubscriptionSpec executeSubscription(RequestInput requestInput) {
FluxExchangeResult<TestExecutionResult> exchangeResult = this.client.post()
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.TEXT_EVENT_STREAM).bodyValue(requestInput)
.exchange().expectStatus().isOk().expectHeader().contentType(MediaType.TEXT_EVENT_STREAM)
.returnResult(TestExecutionResult.class);
return new DefaultSubscriptionSpec(exchangeResult.getResponseBody().cast(ExecutionResult.class),
this.jsonPathConfig, exchangeResult::assertWithDiagnostics);
}
}
/**
* {@link RequestStrategy} that performs requests directly on
* {@link WebGraphQlHandler}, i.e. Web request testing without a transport.
*/
private static class WebGraphQlHandlerRequestStrategy extends AbstractDirectRequestStrategy {
private static final URI DEFAULT_URL = URI.create("http://localhost:8080/graphql");
private static final HttpHeaders DEFAULT_HEADERS = new HttpHeaders();
private final WebGraphQlHandler graphQlHandler;
WebGraphQlHandlerRequestStrategy(WebGraphQlHandler handler, Configuration jsonPathConfig) {
super(jsonPathConfig);
this.graphQlHandler = handler;
}
protected ExecutionResult executeInternal(RequestInput input) {
WebInput webInput = new WebInput(DEFAULT_URL, DEFAULT_HEADERS, input.toMap(), null);
ExecutionResult result = this.graphQlHandler.handle(webInput).block(DEFAULT_TIMEOUT);
Assert.notNull(result, "Expected ExecutionResult");
return result;
}
}
}

View File

@@ -25,69 +25,17 @@ import graphql.GraphQLError;
import reactor.core.publisher.Flux;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.GraphQlService;
import org.springframework.lang.Nullable;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Main entry point for testing GraphQL with requests performed either as an HTTP client
* via {@link WebTestClient} or directly via a {@link WebGraphQlHandler}.
* Contract for testing GraphQL requests.
*
*
* <p>
* GraphQL requests to Spring MVC without an HTTP server: <pre class="code">
* &#064;SpringBootTest
* &#064;AutoConfigureMockMvc
* public class MyTests {
*
* private GraphQlTester graphQlTester;
*
* &#064;BeforeEach
* public void setUp(&#064;Autowired MockMvc mockMvc) {
* WebTestClient client = MockMvcWebTestClient.bindTo(mockMvc).baseUrl("/graphql").build();
* this.graphQlTester = GraphQlTester.create(client);
* }
* </pre>
*
* <p>
* GraphQL requests to Spring WebFlux without an HTTP server: <pre class="code">
* &#064;SpringBootTest
* &#064;AutoConfigureWebTestClient
* public class MyTests {
*
* private GraphQlTester graphQlTester;
*
* &#064;BeforeEach
* public void setUp(&#064;Autowired WebTestClient client) {
* this.graphQlTester = GraphQlTester.create(client);
* }
* </pre>
*
* <p>
* GraphQL requests to a running server: <pre class="code">
* &#064;SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
* public class MyTests {
*
* private GraphQlTester graphQlTester;
*
* &#064;BeforeEach
* public void setUp(&#064;Autowired WebTestClient client) {
* this.graphQlTester = GraphQlTester.create(client);
* }
* </pre>
*
* <p>
* GraphQL requests to any {@link WebGraphQlHandler}: <pre class="code">
* &#064;SpringBootTest
* public class MyTests {
*
* private GraphQlTester graphQlTester;
*
* &#064;BeforeEach
* public void setUp(&#064;Autowired WebGraphQLHandler handler) {
* this.graphQlTester = GraphQlTester.create(handler);
* }
* </pre>
* <p>The workflow declared to prepare, execute, and verify requests is not tied
* to any specific underlying transport. Use {@link WebGraphQlTester} to test
* GraphQL requests over a Web transport. This class can also be used to perform
* calls directly on {@link graphql.GraphQL}, without a transport, via
* {@link GraphQlService}.
*
* @author Rossen Stoyanchev
* @since 1.0.0
@@ -103,28 +51,18 @@ public interface GraphQlTester {
*/
RequestSpec query(String query);
/**
* Create a {@code GraphQlTester} that performs GraphQL requests as an HTTP client
* through the given {@link WebTestClient}. Depending on how the {@code WebTestClient}
* is set up, tests may be with or without a server. See setup examples in class-level
* Javadoc.
* @param client the web client to perform requests with
* @return the created {@code GraphQlTester} instance
*/
static GraphQlTester create(WebTestClient client) {
return new DefaultGraphQlTester(client);
}
/**
* Create a {@code GraphQlTester} that performs GraphQL requests through the given
* {@link WebGraphQlHandler}.
* @param handler the handler to execute requests with
* @return the created {@code GraphQlTester} instance
* {@link GraphQlService}.
* @param service the service to execute requests with
* @return the created {@code GraphQlTester}
*/
static GraphQlTester create(WebGraphQlHandler handler) {
return new DefaultGraphQlTester(handler);
static GraphQlTester create(GraphQlService service) {
return new DefaultGraphQlTester(service);
}
/**
* Declare options to perform a GraphQL request.
*/

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2021 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.test.tester;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Main entry point for testing GraphQL over a Web transport with requests
* executed {@link #create(WebTestClient) via WebTestClient} or
* {@link #create(WebGraphQlHandler) via WebGraphQlHandler}. See the below for
* examples with different scenarios.
*
* <p>
* GraphQL requests to Spring MVC without an HTTP server: <pre class="code">
* &#064;SpringBootTest
* &#064;AutoConfigureMockMvc
* &#064;AutoConfigureGraphQlTester
* public class MyTests {
*
* &#064;Autowired
* private WebGraphQlTester graphQlTester;
* }
* </pre>
*
* <p>
* GraphQL requests to Spring WebFlux without an HTTP server: <pre class="code">
* &#064;SpringBootTest
* &#064;AutoConfigureWebTestClient
* &#064;AutoConfigureGraphQlTester
* public class MyTests {
*
* &#064;Autowired
* private WebGraphQlTester graphQlTester;
* }
* </pre>
*
* <p>
* GraphQL requests to a running server: <pre class="code">
* &#064;SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
* &#064;AutoConfigureWebTestClient
* &#064;AutoConfigureGraphQlTester
* public class MyTests {
*
* &#064;Autowired
* private WebGraphQlTester graphQlTester;
* }
* </pre>
*
* <p>
* GraphQL requests handled directly through a {@link WebGraphQlHandler}: <pre class="code">
* &#064;SpringBootTest
* public class MyTests {
*
* private WebGraphQlTester graphQlTester;
*
* &#064;BeforeEach
* public void setUp(&#064;Autowired WebGraphQLHandler handler) {
* this.graphQlTester = GraphQlTester.create(handler);
* }
* }
* </pre>
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public interface WebGraphQlTester extends GraphQlTester {
/**
* Create a {@code WebGraphQlTester} that performs GraphQL requests as an
* HTTP client through the given {@link WebTestClient}. Depending on how the
* {@code WebTestClient} is set up, tests may be with or without a server.
* See setup examples in class-level Javadoc.
* @param client the web client to perform requests with
* @return the created {@code WebGraphQlTester}
*/
static WebGraphQlTester create(WebTestClient client) {
return new DefaultWebGraphQlTester(client);
}
/**
* Create a {@code WebGraphQlTester} that performs GraphQL requests through
* the given {@link WebGraphQlHandler}.
* @param handler the handler to execute requests with
* @return the created {@code WebGraphQlTester}
*/
static WebGraphQlTester create(WebGraphQlHandler handler) {
return new DefaultWebGraphQlTester(handler);
}
}

View File

@@ -21,34 +21,23 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.language.SourceLocation;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
import org.springframework.graphql.web.WebOutput;
import org.springframework.http.HttpHeaders;
import org.springframework.graphql.GraphQlService;
import org.springframework.lang.Nullable;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -56,11 +45,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link GraphQlTester} parameterized to:
* <ul>
* <li>Connect to {@link MockWebServer} and return a preset HTTP response.
* <li>Use mock {@link WebGraphQlHandler} to return a preset {@link ExecutionResult}.
* </ul>
* Tests for {@link GraphQlTester}.
*
* <p>
* There is no actual handling via {@link graphql.GraphQL} in either scenario. The main
@@ -70,35 +55,36 @@ public class GraphQlTesterTests {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static Stream<GraphQlTesterSetup> argumentSource() {
return Stream.of(new MockWebServerSetup(), new MockWebGraphQlHandlerSetup());
}
@ParameterizedTest
@MethodSource("argumentSource")
void pathAndValueExistsAndEmptyChecks(GraphQlTesterSetup setup) throws Exception {
private final GraphQlService service = mock(GraphQlService.class);
private final ArgumentCaptor<ExecutionInput> inputCaptor = ArgumentCaptor.forClass(ExecutionInput.class);
private final GraphQlTester graphQlTester = GraphQlTester.create(this.service);
@Test
void pathAndValueExistsAndEmptyChecks() throws Exception {
String query = "{me {name, friends}}";
setup.response("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}");
setResponse("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute();
spec.path("me.name").pathExists().valueExists().valueIsNotEmpty();
spec.path("me.friends").valueIsEmpty();
spec.path("hero").pathDoesNotExist().valueDoesNotExist().valueIsEmpty();
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void matchesJson(GraphQlTesterSetup setup) throws Exception {
@Test
void matchesJson() throws Exception {
String query = "{me {name}}";
setup.response("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}");
setResponse("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute();
spec.path("").matchesJson("{\"me\": {\"name\":\"Luke Skywalker\",\"friends\":[]}}");
spec.path("me").matchesJson("{\"name\":\"Luke Skywalker\"}");
@@ -108,18 +94,16 @@ public class GraphQlTesterTests {
assertThatThrownBy(() -> spec.path("me").matchesJsonStrictly("{\"friends\":[]}"))
.as("Extended fields should fail in strict mode").hasMessageContaining("Unexpected: name");
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void entity(GraphQlTesterSetup setup) throws Exception {
@Test
void entity() throws Exception {
String query = "{me {name}}";
setup.response("{\"me\": {\"name\":\"Luke Skywalker\"}}");
setResponse("{\"me\": {\"name\":\"Luke Skywalker\"}}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute();
MovieCharacter luke = MovieCharacter.create("Luke Skywalker");
MovieCharacter han = MovieCharacter.create("Han Solo");
@@ -134,19 +118,17 @@ public class GraphQlTesterTests {
spec.path("").entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() {
}).isEqualTo(Collections.singletonMap("me", luke));
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void entityList(GraphQlTesterSetup setup) throws Exception {
@Test
void entityList() throws Exception {
String query = "{me {name, friends}}";
setup.response("{" + " \"me\":{" + " \"name\":\"Luke Skywalker\","
setResponse("{" + " \"me\":{" + " \"name\":\"Luke Skywalker\","
+ " \"friends\":[{\"name\":\"Han Solo\"}, {\"name\":\"Leia Organa\"}]" + " }" + "}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).execute();
MovieCharacter han = MovieCharacter.create("Han Solo");
MovieCharacter leia = MovieCharacter.create("Leia Organa");
@@ -161,102 +143,89 @@ public class GraphQlTesterTests {
spec.path("me.friends").entityList(new ParameterizedTypeReference<MovieCharacter>() {
}).containsExactly(han, leia);
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void operationNameAndVariables(GraphQlTesterSetup setup) throws Exception {
@Test
void operationNameAndVariables() throws Exception {
String query = "query HeroNameAndFriends($episode: Episode) {" + " hero(episode: $episode) {" + " name"
+ " }" + "}";
setup.response("{\"hero\": {\"name\":\"R2-D2\"}}");
setResponse("{\"hero\": {\"name\":\"R2-D2\"}}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).operationName("HeroNameAndFriends")
GraphQlTester.ResponseSpec spec = this.graphQlTester.query(query).operationName("HeroNameAndFriends")
.variable("episode", "JEDI").variables((map) -> map.put("foo", "bar")).execute();
spec.path("hero").entity(MovieCharacter.class).isEqualTo(MovieCharacter.create("R2-D2"));
setup.verifyRequest((input) -> {
assertThat(input.getQuery()).contains(query);
assertThat(input.getOperationName()).isEqualTo("HeroNameAndFriends");
assertThat(input.getVariables()).hasSize(2);
assertThat(input.getVariables()).containsEntry("episode", "JEDI");
assertThat(input.getVariables()).containsEntry("foo", "bar");
});
setup.shutdown();
ExecutionInput input = this.inputCaptor.getValue();
assertThat(input.getQuery()).contains(query);
assertThat(input.getOperationName()).isEqualTo("HeroNameAndFriends");
assertThat(input.getVariables()).hasSize(2);
assertThat(input.getVariables()).containsEntry("episode", "JEDI");
assertThat(input.getVariables()).containsEntry("foo", "bar");
}
@ParameterizedTest
@MethodSource("argumentSource")
void errorsCheckedOnExecuteAndVerify(GraphQlTesterSetup setup) throws Exception {
@Test
void errorsCheckedOnExecuteAndVerify() throws Exception {
String query = "{me {name, friends}}";
setup.response(GraphqlErrorBuilder.newError().message("Invalid query").build());
setResponse(GraphqlErrorBuilder.newError().message("Invalid query").build());
assertThatThrownBy(() -> setup.graphQlTester().query(query).executeAndVerify())
assertThatThrownBy(() -> this.graphQlTester.query(query).executeAndVerify())
.hasMessageContaining("Response has 1 unexpected error(s).");
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void errorsCheckedOnTraverse(GraphQlTesterSetup setup) throws Exception {
@Test
void errorsCheckedOnTraverse() throws Exception {
String query = "{me {name, friends}}";
setup.response(GraphqlErrorBuilder.newError().message("Invalid query").build());
setResponse(GraphqlErrorBuilder.newError().message("Invalid query").build());
assertThatThrownBy(() -> setup.graphQlTester().query(query).execute().path("me"))
assertThatThrownBy(() -> this.graphQlTester.query(query).execute().path("me"))
.hasMessageContaining("Response has 1 unexpected error(s).");
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void errorsPartiallyFiltered(GraphQlTesterSetup setup) throws Exception {
@Test
void errorsPartiallyFiltered() throws Exception {
String query = "{me {name, friends}}";
setup.response(GraphqlErrorBuilder.newError().message("some error").build(),
setResponse(GraphqlErrorBuilder.newError().message("some error").build(),
GraphqlErrorBuilder.newError().message("some other error").build());
assertThatThrownBy(() -> setup.graphQlTester().query(query).execute().errors()
assertThatThrownBy(() -> this.graphQlTester.query(query).execute().errors()
.filter((error) -> error.getMessage().equals("some error")).verify())
.hasMessageContaining("Response has 1 unexpected error(s) of 2 total.");
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void errorsFiltered(GraphQlTesterSetup setup) throws Exception {
@Test
void errorsFiltered() throws Exception {
String query = "{me {name, friends}}";
setup.response(GraphqlErrorBuilder.newError().message("some error").build(),
setResponse(GraphqlErrorBuilder.newError().message("some error").build(),
GraphqlErrorBuilder.newError().message("some other error").build());
setup.graphQlTester().query(query).execute().errors().filter((error) -> error.getMessage().startsWith("some "))
this.graphQlTester.query(query).execute().errors().filter((error) -> error.getMessage().startsWith("some "))
.verify().path("me").pathDoesNotExist();
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
@ParameterizedTest
@MethodSource("argumentSource")
void errorsConsumed(GraphQlTesterSetup setup) throws Exception {
@Test
void errorsConsumed() throws Exception {
String query = "{me {name, friends}}";
setup.response(
setResponse(
GraphqlErrorBuilder.newError().message("Invalid query").location(new SourceLocation(1, 2)).build());
setup.graphQlTester().query(query).execute().errors().satisfy((errors) -> {
this.graphQlTester.query(query).execute().errors().satisfy((errors) -> {
assertThat(errors).hasSize(1);
assertThat(errors.get(0).getMessage()).isEqualTo("Invalid query");
assertThat(errors.get(0).getLocations()).hasSize(1);
@@ -264,134 +233,31 @@ public class GraphQlTesterTests {
assertThat(errors.get(0).getLocations().get(0).getColumn()).isEqualTo(2);
}).path("me").pathDoesNotExist();
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
assertThat(getActualQuery()).contains(query);
}
private interface GraphQlTesterSetup {
GraphQlTester graphQlTester();
default void response(String data) throws Exception {
response(data, Collections.emptyList());
}
default void response(GraphQLError... errors) throws Exception {
response(null, Arrays.asList(errors));
}
void response(@Nullable String data, List<GraphQLError> errors) throws Exception;
void verifyRequest(Consumer<WebInput> consumer) throws Exception;
default void shutdown() throws Exception {
// no-op by default
}
private void setResponse(String data) throws Exception {
setResponse(data, Collections.emptyList());
}
private static class MockWebServerSetup implements GraphQlTesterSetup {
private final MockWebServer server;
private final GraphQlTester graphQlTester;
MockWebServerSetup() {
this.server = new MockWebServer();
this.graphQlTester = GraphQlTester.create(initWebTestClient(this.server));
}
private static WebTestClient initWebTestClient(MockWebServer server) {
String baseUrl = server.url("/graphQL").toString();
return WebTestClient.bindToServer().baseUrl(baseUrl).build();
}
@Override
public GraphQlTester graphQlTester() {
return this.graphQlTester;
}
@Override
public void response(@Nullable String data, List<GraphQLError> errors) throws Exception {
StringBuilder sb = new StringBuilder("{");
if (StringUtils.hasText(data)) {
sb.append("\"data\":").append(data);
}
if (!CollectionUtils.isEmpty(errors)) {
List<Map<String, Object>> errorSpecs = errors.stream().map(GraphQLError::toSpecification)
.collect(Collectors.toList());
sb.append(StringUtils.hasText(data) ? ", " : "").append("\"errors\":")
.append(OBJECT_MAPPER.writeValueAsString(errorSpecs));
}
sb.append("}");
MockResponse response = new MockResponse();
response.setHeader("Content-Type", "application/json");
response.setBody(sb.toString());
this.server.enqueue(response);
}
@Override
public void verifyRequest(Consumer<WebInput> consumer) throws Exception {
assertThat(this.server.getRequestCount()).isEqualTo(1);
RecordedRequest request = this.server.takeRequest();
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
String content = request.getBody().readUtf8();
Map<String, Object> map = new ObjectMapper().readValue(content, new TypeReference<Map<String, Object>>() {
});
WebInput webInput = new WebInput(request.getRequestUrl().uri(), new HttpHeaders(), map, null);
consumer.accept(webInput);
}
@Override
public void shutdown() throws Exception {
this.server.shutdown();
}
private void setResponse(GraphQLError... errors) throws Exception {
setResponse(null, Arrays.asList(errors));
}
private static class MockWebGraphQlHandlerSetup implements GraphQlTesterSetup {
private final WebGraphQlHandler handler = mock(WebGraphQlHandler.class);
private final ArgumentCaptor<WebInput> bodyCaptor = ArgumentCaptor.forClass(WebInput.class);
private final GraphQlTester graphQlTester;
MockWebGraphQlHandlerSetup() {
this.graphQlTester = GraphQlTester.create(this.handler);
private void setResponse(@Nullable String data, List<GraphQLError> errors) throws Exception {
ExecutionResultImpl.Builder builder = new ExecutionResultImpl.Builder();
if (data != null) {
builder.data(OBJECT_MAPPER.readValue(data, new TypeReference<Map<String, Object>>() {}));
}
@Override
public GraphQlTester graphQlTester() {
return this.graphQlTester;
}
@Override
public void response(@Nullable String data, List<GraphQLError> errors) throws Exception {
ExecutionResultImpl.Builder builder = new ExecutionResultImpl.Builder();
if (data != null) {
builder.data(OBJECT_MAPPER.readValue(data, new TypeReference<Map<String, Object>>() {
}));
}
if (!CollectionUtils.isEmpty(errors)) {
builder.addErrors(errors);
}
ExecutionResult result = builder.build();
WebOutput output = new WebOutput(mock(WebInput.class), result);
given(this.handler.handle(this.bodyCaptor.capture())).willReturn(Mono.just(output));
}
@Override
public void verifyRequest(Consumer<WebInput> consumer) {
WebInput webInput = this.bodyCaptor.getValue();
consumer.accept(webInput);
if (!CollectionUtils.isEmpty(errors)) {
builder.addErrors(errors);
}
ExecutionResult result = builder.build();
given(this.service.execute(this.inputCaptor.capture())).willReturn(Mono.just(result));
}
private String getActualQuery() {
return this.inputCaptor.getValue().getQuery();
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright 2002-2021 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.test.tester;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
import org.springframework.graphql.web.WebOutput;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link WebGraphQlTester} parameterized to:
* <ul>
* <li>Connect to {@link MockWebServer} and return a preset HTTP response.
* <li>Use mock {@link WebGraphQlHandler} to return a preset {@link ExecutionResult}.
* </ul>
*
* <p>
* There is no actual handling via {@link graphql.GraphQL} in either scenario. The main
* focus is to verify {@link GraphQlTester} request preparation and response handling.
*/
public class WebGraphQlTesterTests {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static Stream<GraphQlTesterSetup> argumentSource() {
return Stream.of(new MockWebServerSetup(), new MockWebGraphQlHandlerSetup());
}
@ParameterizedTest
@MethodSource("argumentSource")
void pathAndValueExistsAndEmptyChecks(GraphQlTesterSetup setup) throws Exception {
String query = "{me {name, friends}}";
setup.response("{\"me\": {\"name\":\"Luke Skywalker\", \"friends\":[]}}");
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
spec.path("me.name").pathExists().valueExists().valueIsNotEmpty();
spec.path("me.friends").valueIsEmpty();
spec.path("hero").pathDoesNotExist().valueDoesNotExist().valueIsEmpty();
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
setup.shutdown();
}
private interface GraphQlTesterSetup {
GraphQlTester graphQlTester();
default void response(String data) throws Exception {
response(data, Collections.emptyList());
}
default void response(GraphQLError... errors) throws Exception {
response(null, Arrays.asList(errors));
}
void response(@Nullable String data, List<GraphQLError> errors) throws Exception;
void verifyRequest(Consumer<WebInput> consumer) throws Exception;
default void shutdown() throws Exception {
// no-op by default
}
}
private static class MockWebServerSetup implements GraphQlTesterSetup {
private final MockWebServer server;
private final GraphQlTester graphQlTester;
MockWebServerSetup() {
this.server = new MockWebServer();
this.graphQlTester = WebGraphQlTester.create(initWebTestClient(this.server));
}
private static WebTestClient initWebTestClient(MockWebServer server) {
String baseUrl = server.url("/graphQL").toString();
return WebTestClient.bindToServer().baseUrl(baseUrl).build();
}
@Override
public GraphQlTester graphQlTester() {
return this.graphQlTester;
}
@Override
public void response(@Nullable String data, List<GraphQLError> errors) throws Exception {
StringBuilder sb = new StringBuilder("{");
if (StringUtils.hasText(data)) {
sb.append("\"data\":").append(data);
}
if (!CollectionUtils.isEmpty(errors)) {
List<Map<String, Object>> errorSpecs = errors.stream().map(GraphQLError::toSpecification)
.collect(Collectors.toList());
sb.append(StringUtils.hasText(data) ? ", " : "").append("\"errors\":")
.append(OBJECT_MAPPER.writeValueAsString(errorSpecs));
}
sb.append("}");
MockResponse response = new MockResponse();
response.setHeader("Content-Type", "application/json");
response.setBody(sb.toString());
this.server.enqueue(response);
}
@Override
public void verifyRequest(Consumer<WebInput> consumer) throws Exception {
assertThat(this.server.getRequestCount()).isEqualTo(1);
RecordedRequest request = this.server.takeRequest();
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
String content = request.getBody().readUtf8();
Map<String, Object> map = new ObjectMapper().readValue(content, new TypeReference<Map<String, Object>>() {
});
WebInput webInput = new WebInput(request.getRequestUrl().uri(), new HttpHeaders(), map, null);
consumer.accept(webInput);
}
@Override
public void shutdown() throws Exception {
this.server.shutdown();
}
}
private static class MockWebGraphQlHandlerSetup implements GraphQlTesterSetup {
private final WebGraphQlHandler handler = mock(WebGraphQlHandler.class);
private final ArgumentCaptor<WebInput> bodyCaptor = ArgumentCaptor.forClass(WebInput.class);
private final GraphQlTester graphQlTester;
MockWebGraphQlHandlerSetup() {
this.graphQlTester = WebGraphQlTester.create(this.handler);
}
@Override
public GraphQlTester graphQlTester() {
return this.graphQlTester;
}
@Override
public void response(@Nullable String data, List<GraphQLError> errors) throws Exception {
ExecutionResultImpl.Builder builder = new ExecutionResultImpl.Builder();
if (data != null) {
builder.data(OBJECT_MAPPER.readValue(data, new TypeReference<Map<String, Object>>() {
}));
}
if (!CollectionUtils.isEmpty(errors)) {
builder.addErrors(errors);
}
ExecutionResult result = builder.build();
WebOutput output = new WebOutput(mock(WebInput.class), result);
given(this.handler.handle(this.bodyCaptor.capture())).willReturn(Mono.just(output));
}
@Override
public void verifyRequest(Consumer<WebInput> consumer) {
WebInput webInput = this.bodyCaptor.getValue();
consumer.accept(webInput);
}
}
}