Apply Spring JavaFormat to Spring GraphQL Test
See gh-54
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
plugins {
|
||||
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
|
||||
id 'java-library'
|
||||
id "org.springframework.graphql.conventions"
|
||||
}
|
||||
|
||||
description = "Spring Support for Testing GraphQL Applications"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -61,6 +62,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link GraphQlTester}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@@ -68,16 +71,14 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = DefaultGraphQlTester.class.getClassLoader();
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
|
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
}
|
||||
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
|
||||
private final Configuration jsonPathConfig;
|
||||
|
||||
|
||||
DefaultGraphQlTester(WebTestClient client) {
|
||||
this.jsonPathConfig = initJsonPathConfig();
|
||||
this.requestStrategy = new WebTestClientRequestStrategy(client, this.jsonPathConfig);
|
||||
@@ -92,13 +93,11 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
return (jackson2Present ? Jackson2Configuration.create() : Configuration.builder().build());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequestSpec query(String query) {
|
||||
return new DefaultRequestSpec(query);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulate how a GraphQL request is performed.
|
||||
*/
|
||||
@@ -106,21 +105,24 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
/**
|
||||
* Perform a request with the given {@link RequestInput} container.
|
||||
* @param input the request input
|
||||
* @return the response spec
|
||||
*/
|
||||
GraphQlTester.ResponseSpec execute(RequestInput input);
|
||||
|
||||
/**
|
||||
* Perform a subscription with the given {@link RequestInput} container.
|
||||
* @param input the request input
|
||||
* @return the subscription spec
|
||||
*/
|
||||
GraphQlTester.SubscriptionSpec executeSubscription(RequestInput input);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
* {@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 {
|
||||
|
||||
@@ -135,14 +137,9 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@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();
|
||||
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");
|
||||
@@ -155,20 +152,15 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
@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)
|
||||
.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),
|
||||
return new DefaultSubscriptionSpec(exchangeResult.getResponseBody().cast(ExecutionResult.class),
|
||||
this.jsonPathConfig, exchangeResult::assertWithDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RequestStrategy} that performs requests directly on {@link GraphQL}.
|
||||
@@ -181,12 +173,11 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
private final WebGraphQlHandler graphQlHandler;
|
||||
|
||||
private final Configuration jsonPathConfig;
|
||||
|
||||
public DirectRequestStrategy(WebGraphQlHandler handler, Configuration jsonPathConfig) {
|
||||
DirectRequestStrategy(WebGraphQlHandler handler, Configuration jsonPathConfig) {
|
||||
this.graphQlHandler = handler;
|
||||
this.jsonPathConfig = jsonPathConfig;
|
||||
}
|
||||
@@ -205,8 +196,9 @@ 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);
|
||||
}
|
||||
@@ -219,7 +211,7 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
|
||||
private Consumer<Runnable> assertDecorator(RequestInput input) {
|
||||
return assertion -> {
|
||||
return (assertion) -> {
|
||||
try {
|
||||
assertion.run();
|
||||
}
|
||||
@@ -228,13 +220,13 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RequestSpec} that collects the query, operationName, and variables.
|
||||
*/
|
||||
private class DefaultRequestSpec implements RequestSpec {
|
||||
private final class DefaultRequestSpec implements RequestSpec {
|
||||
|
||||
private final String query;
|
||||
|
||||
@@ -284,12 +276,12 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
RequestInput input = new RequestInput(this.query, this.operationName, this.variables);
|
||||
return DefaultGraphQlTester.this.requestStrategy.executeSubscription(input);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ErrorsContainer {
|
||||
|
||||
private static final Predicate<GraphQLError> MATCH_ALL_PREDICATE = error -> true;
|
||||
private static final Predicate<GraphQLError> MATCH_ALL_PREDICATE = (error) -> true;
|
||||
|
||||
private final List<TestGraphQlError> errors;
|
||||
|
||||
@@ -302,32 +294,35 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.assertDecorator = assertDecorator;
|
||||
}
|
||||
|
||||
public void doAssert(Runnable task) {
|
||||
void doAssert(Runnable task) {
|
||||
this.assertDecorator.accept(task);
|
||||
}
|
||||
|
||||
public void filterErrors(Predicate<GraphQLError> errorPredicate) {
|
||||
this.errors.forEach(error -> error.filter(errorPredicate));
|
||||
void filterErrors(Predicate<GraphQLError> errorPredicate) {
|
||||
this.errors.forEach((error) -> error.filter(errorPredicate));
|
||||
}
|
||||
|
||||
public void consumeErrors(Consumer<List<GraphQLError>> consumer) {
|
||||
void consumeErrors(Consumer<List<GraphQLError>> consumer) {
|
||||
filterErrors(MATCH_ALL_PREDICATE);
|
||||
consumer.accept(new ArrayList<>(this.errors));
|
||||
}
|
||||
|
||||
public void verifyErrors() {
|
||||
void verifyErrors() {
|
||||
|
||||
List<TestGraphQlError> unexpected =
|
||||
this.errors.stream().filter(error -> !error.isExpected()).collect(Collectors.toList());
|
||||
List<TestGraphQlError> unexpected = this.errors.stream().filter((error) -> !error.isExpected())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
this.assertDecorator.accept(() -> AssertionErrors.assertTrue(
|
||||
"Response has " + unexpected.size() + " unexpected error(s)" +
|
||||
(unexpected.size() != this.errors.size() ? " of " + this.errors.size() + " total" : "") + ". " +
|
||||
"If expected, please use ResponseSpec#errors to filter them out: " + unexpected,
|
||||
CollectionUtils.isEmpty(unexpected)));
|
||||
this.assertDecorator
|
||||
.accept(() -> AssertionErrors.assertTrue(
|
||||
"Response has " + unexpected.size() + " unexpected error(s)"
|
||||
+ ((unexpected.size() != this.errors.size())
|
||||
? " of " + this.errors.size() + " total" : "")
|
||||
+ ". " + "If expected, please use ResponseSpec#errors to filter them out: "
|
||||
+ unexpected,
|
||||
CollectionUtils.isEmpty(unexpected)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for a GraphQL response with access to data and errors.
|
||||
@@ -349,18 +344,19 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
private static List<TestGraphQlError> readErrors(DocumentContext documentContext) {
|
||||
Assert.notNull(documentContext, "DocumentContext is required");
|
||||
try {
|
||||
return documentContext.read(ERRORS_PATH, new TypeRef<List<TestGraphQlError>>() {});
|
||||
return documentContext.read(ERRORS_PATH, new TypeRef<List<TestGraphQlError>>() {
|
||||
});
|
||||
}
|
||||
catch (PathNotFoundException ex) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public String jsonContent() {
|
||||
String jsonContent() {
|
||||
return this.jsonContent;
|
||||
}
|
||||
|
||||
public String jsonContent(JsonPath jsonPath) {
|
||||
String jsonContent(JsonPath jsonPath) {
|
||||
try {
|
||||
Object content = this.documentContext.read(jsonPath);
|
||||
return this.documentContext.configuration().jsonProvider().toJson(content);
|
||||
@@ -370,25 +366,24 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T read(JsonPath jsonPath, TypeRef<T> typeRef) {
|
||||
<T> T read(JsonPath jsonPath, TypeRef<T> typeRef) {
|
||||
return this.documentContext.read(jsonPath, typeRef);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResponseSpec} that operates on the response from a GraphQL HTTP request.
|
||||
*/
|
||||
private static class DefaultResponseSpec implements ResponseSpec, ErrorSpec {
|
||||
private static final class DefaultResponseSpec implements ResponseSpec, ErrorSpec {
|
||||
|
||||
private final ResponseContainer responseContainer;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
* @param documentContext the parsed response content
|
||||
* @param assertDecorator decorator to apply around assertions, e.g. to
|
||||
* add extra contextual information such as HTTP request and response
|
||||
* body details
|
||||
* @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) {
|
||||
this.responseContainer = new ResponseContainer(documentContext, assertDecorator);
|
||||
@@ -422,8 +417,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.responseContainer.consumeErrors(consumer);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PathSpec} implementation.
|
||||
@@ -438,7 +433,6 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final JsonPathExpectationsHelper pathHelper;
|
||||
|
||||
|
||||
DefaultPathSpec(String path, ResponseContainer responseContainer) {
|
||||
Assert.notNull(path, "`path` is required");
|
||||
Assert.notNull(responseContainer, "ResponseContainer is required");
|
||||
@@ -458,7 +452,6 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
return JsonPath.compile(path);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PathSpec path(String path) {
|
||||
return new DefaultPathSpec(path, this.responseContainer);
|
||||
@@ -466,29 +459,26 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public PathSpec pathExists() {
|
||||
this.responseContainer.doAssert(
|
||||
() -> this.pathHelper.hasJsonPath(this.responseContainer.jsonContent()));
|
||||
this.responseContainer.doAssert(() -> this.pathHelper.hasJsonPath(this.responseContainer.jsonContent()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec pathDoesNotExist() {
|
||||
this.responseContainer.doAssert(
|
||||
() -> this.pathHelper.doesNotHaveJsonPath(this.responseContainer.jsonContent()));
|
||||
this.responseContainer
|
||||
.doAssert(() -> this.pathHelper.doesNotHaveJsonPath(this.responseContainer.jsonContent()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec valueExists() {
|
||||
this.responseContainer.doAssert(
|
||||
() -> this.pathHelper.exists(this.responseContainer.jsonContent()));
|
||||
this.responseContainer.doAssert(() -> this.pathHelper.exists(this.responseContainer.jsonContent()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec valueDoesNotExist() {
|
||||
this.responseContainer.doAssert(
|
||||
() -> this.pathHelper.doesNotExist(this.responseContainer.jsonContent()));
|
||||
this.responseContainer.doAssert(() -> this.pathHelper.doesNotExist(this.responseContainer.jsonContent()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -507,8 +497,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public PathSpec valueIsNotEmpty() {
|
||||
this.responseContainer.doAssert(
|
||||
() -> this.pathHelper.assertValueIsNotEmpty(this.responseContainer.jsonContent()));
|
||||
this.responseContainer
|
||||
.doAssert(() -> this.pathHelper.assertValueIsNotEmpty(this.responseContainer.jsonContent()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -555,18 +545,17 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
new JsonExpectationsHelper().assertJsonEqual(expected, actual, strict);
|
||||
}
|
||||
catch (AssertionError ex) {
|
||||
throw new AssertionError(ex.getMessage() + "\n\n" +
|
||||
"Expected JSON content:\n'" + expected + "'\n\n" +
|
||||
"Actual JSON content:\n'" + actual + "'\n\n" +
|
||||
"Input path: '" + this.inputPath + "'\n", ex);
|
||||
throw new AssertionError(ex.getMessage() + "\n\n" + "Expected JSON content:\n'" + expected + "'\n\n"
|
||||
+ "Actual JSON content:\n'" + actual + "'\n\n" + "Input path: '" + this.inputPath + "'\n",
|
||||
ex);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AssertionError("JSON parsing error", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EntitySpec} implementation.
|
||||
@@ -604,22 +593,19 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public <T extends S> T isEqualTo(Object expected) {
|
||||
this.responseContainer.doAssert(
|
||||
() -> AssertionErrors.assertEquals(this.inputPath, expected, this.entity));
|
||||
this.responseContainer.doAssert(() -> AssertionErrors.assertEquals(this.inputPath, expected, this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isNotEqualTo(Object other) {
|
||||
this.responseContainer.doAssert(
|
||||
() -> AssertionErrors.assertNotEquals(this.inputPath, other, this.entity));
|
||||
this.responseContainer.doAssert(() -> AssertionErrors.assertNotEquals(this.inputPath, other, this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends S> T isSameAs(Object expected) {
|
||||
this.responseContainer.doAssert(
|
||||
() -> AssertionErrors.assertTrue(this.inputPath, expected == this.entity));
|
||||
this.responseContainer.doAssert(() -> AssertionErrors.assertTrue(this.inputPath, expected == this.entity));
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -631,8 +617,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public <T extends S> T matches(Predicate<D> predicate) {
|
||||
this.responseContainer.doAssert(
|
||||
() -> AssertionErrors.assertTrue(this.inputPath, predicate.test(this.entity)));
|
||||
this.responseContainer
|
||||
.doAssert(() -> AssertionErrors.assertTrue(this.inputPath, predicate.test(this.entity)));
|
||||
return self();
|
||||
}
|
||||
|
||||
@@ -651,8 +637,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
private <T extends S> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ListEntitySpec} implementation.
|
||||
@@ -669,8 +655,7 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
public ListEntitySpec<E> contains(E... elements) {
|
||||
doAssert(() -> {
|
||||
List<E> expected = Arrays.asList(elements);
|
||||
AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' does not contain " + expected,
|
||||
AssertionErrors.assertTrue("List at path '" + getInputPath() + "' does not contain " + expected,
|
||||
(getEntity() != null && getEntity().containsAll(expected)));
|
||||
});
|
||||
return this;
|
||||
@@ -702,36 +687,32 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public ListEntitySpec<E> hasSize(int size) {
|
||||
doAssert(() ->
|
||||
AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' should have size " + size,
|
||||
(getEntity() != null && getEntity().size() == size)));
|
||||
doAssert(() -> AssertionErrors.assertTrue("List at path '" + getInputPath() + "' should have size " + size,
|
||||
(getEntity() != null && getEntity().size() == size)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListEntitySpec<E> hasSizeLessThan(int boundary) {
|
||||
doAssert(() ->
|
||||
AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' should have size less than " + boundary,
|
||||
(getEntity() != null && getEntity().size() < boundary)));
|
||||
doAssert(() -> AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' should have size less than " + boundary,
|
||||
(getEntity() != null && getEntity().size() < boundary)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListEntitySpec<E> hasSizeGreaterThan(int boundary) {
|
||||
doAssert(() ->
|
||||
AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' should have size greater than " + boundary,
|
||||
(getEntity() != null && getEntity().size() > boundary)));
|
||||
doAssert(() -> AssertionErrors.assertTrue(
|
||||
"List at path '" + getInputPath() + "' should have size greater than " + boundary,
|
||||
(getEntity() != null && getEntity().size() > boundary)));
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link SubscriptionSpec} implementation that operates on a
|
||||
* {@link Publisher} of {@link ExecutionResult}.
|
||||
* {@link SubscriptionSpec} implementation that operates on a {@link Publisher} of
|
||||
* {@link ExecutionResult}.
|
||||
*/
|
||||
private static class DefaultSubscriptionSpec implements SubscriptionSpec {
|
||||
|
||||
@@ -741,8 +722,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final Consumer<Runnable> assertDecorator;
|
||||
|
||||
<T> DefaultSubscriptionSpec(
|
||||
Publisher<ExecutionResult> publisher, Configuration jsonPathConfig, Consumer<Runnable> decorator) {
|
||||
<T> DefaultSubscriptionSpec(Publisher<ExecutionResult> publisher, Configuration jsonPathConfig,
|
||||
Consumer<Runnable> decorator) {
|
||||
|
||||
this.publisher = publisher;
|
||||
this.jsonPathConfig = jsonPathConfig;
|
||||
@@ -751,22 +732,21 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public Flux<ResponseSpec> toFlux() {
|
||||
return Flux.from(this.publisher).map(result -> {
|
||||
return Flux.from(this.publisher).map((result) -> {
|
||||
DocumentContext context = JsonPath.parse(result.toSpecification(), this.jsonPathConfig);
|
||||
return new DefaultResponseSpec(context, this.assertDecorator);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Jackson2Configuration {
|
||||
|
||||
static Configuration create() {
|
||||
return Configuration.builder()
|
||||
.jsonProvider(new JacksonJsonProvider())
|
||||
.mappingProvider(new JacksonMappingProvider())
|
||||
.build();
|
||||
return Configuration.builder().jsonProvider(new JacksonJsonProvider())
|
||||
.mappingProvider(new JacksonMappingProvider()).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.List;
|
||||
@@ -29,13 +30,12 @@ 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}.
|
||||
* Main entry point for testing GraphQL with requests performed either as an HTTP client
|
||||
* via {@link WebTestClient} or directly via a {@link WebGraphQlHandler}.
|
||||
*
|
||||
*
|
||||
* <p>GraphQL requests to Spring MVC without an HTTP server:
|
||||
* <pre class="code">
|
||||
* <p>
|
||||
* GraphQL requests to Spring MVC without an HTTP server: <pre class="code">
|
||||
* @SpringBootTest
|
||||
* @AutoConfigureMockMvc
|
||||
* public class MyTests {
|
||||
@@ -49,8 +49,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>GraphQL requests to Spring WebFlux without an HTTP server:
|
||||
* <pre class="code">
|
||||
* <p>
|
||||
* GraphQL requests to Spring WebFlux without an HTTP server: <pre class="code">
|
||||
* @SpringBootTest
|
||||
* @AutoConfigureWebTestClient
|
||||
* public class MyTests {
|
||||
@@ -63,8 +63,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>GraphQL requests to a running server:
|
||||
* <pre class="code">
|
||||
* <p>
|
||||
* GraphQL requests to a running server: <pre class="code">
|
||||
* @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
* public class MyTests {
|
||||
*
|
||||
@@ -76,8 +76,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>GraphQL requests to any {@link WebGraphQlHandler}:
|
||||
* <pre class="code">
|
||||
* <p>
|
||||
* GraphQL requests to any {@link WebGraphQlHandler}: <pre class="code">
|
||||
* @SpringBootTest
|
||||
* public class MyTests {
|
||||
*
|
||||
@@ -88,24 +88,26 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* this.graphQlTester = GraphQlTester.create(handler);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Prepare to perform a GraphQL request with the given operation which may
|
||||
* be a query, mutation, or a subscription.
|
||||
* Prepare to perform a GraphQL request with the given operation which may be a query,
|
||||
* mutation, or a subscription.
|
||||
* @param query the operation to be performed
|
||||
* @return spec for response assertions
|
||||
* @throws AssertionError if the response status is not 200 (OK)
|
||||
*/
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
@@ -114,8 +116,8 @@ public interface GraphQlTester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code GraphQlTester} that performs GraphQL requests through
|
||||
* the given {@link WebGraphQlHandler}.
|
||||
* 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
|
||||
*/
|
||||
@@ -123,19 +125,17 @@ public interface GraphQlTester {
|
||||
return new DefaultGraphQlTester(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options to perform a GraphQL request.
|
||||
*/
|
||||
interface ExecuteSpec {
|
||||
|
||||
/**
|
||||
* Execute the GraphQL request and return a spec for further inspection
|
||||
* of response data and errors.
|
||||
*
|
||||
* Execute the GraphQL request and return a spec for further inspection of
|
||||
* response data and errors.
|
||||
* @return options for asserting the response
|
||||
* @throws AssertionError if the request is performed over HTTP and the
|
||||
* response status is not 200 (OK).
|
||||
* @throws AssertionError if the request is performed over HTTP and the response
|
||||
* status is not 200 (OK).
|
||||
*/
|
||||
ResponseSpec execute();
|
||||
|
||||
@@ -145,18 +145,16 @@ public interface GraphQlTester {
|
||||
void executeAndVerify();
|
||||
|
||||
/**
|
||||
* Execute the GraphQL request as a subscription and return a spec with
|
||||
* options to transform the result stream.
|
||||
*
|
||||
* Execute the GraphQL request as a subscription and return a spec with options to
|
||||
* transform the result stream.
|
||||
* @return spec with options to transform the subscription result stream
|
||||
* @throws AssertionError if the request is performed over HTTP and the
|
||||
* response status is not 200 (OK).
|
||||
* @throws AssertionError if the request is performed over HTTP and the response
|
||||
* status is not 200 (OK).
|
||||
*/
|
||||
SubscriptionSpec executeSubscription();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options to gather input for a GraphQL request and execute it.
|
||||
*/
|
||||
@@ -164,20 +162,27 @@ public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Set the operation name.
|
||||
* @param name the operation name
|
||||
* @return this request spec
|
||||
*/
|
||||
RequestSpec operationName(@Nullable String name);
|
||||
|
||||
/**
|
||||
* Add a variable.
|
||||
* @param name the variable name
|
||||
* @param value the variable value
|
||||
* @return this request spec
|
||||
*/
|
||||
RequestSpec variable(String name, Object value);
|
||||
|
||||
/**
|
||||
* Modify variables by accessing the underlying map.
|
||||
* @param variablesConsumer a callback for the map of variables
|
||||
* @return this request spec
|
||||
*/
|
||||
RequestSpec variables(Consumer<Map<String, Object>> variablesConsumer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare options to switch to different part of the GraphQL response.
|
||||
@@ -185,20 +190,19 @@ public interface GraphQlTester {
|
||||
interface TraverseSpec {
|
||||
|
||||
/**
|
||||
* Switch to a path under the "data" section of the GraphQL response.
|
||||
* The path can be an operation root type name, e.g. "project", or a
|
||||
* nested path such as "project.name", or any
|
||||
* Switch to a path under the "data" section of the GraphQL response. The path can
|
||||
* be an operation root type name, e.g. "project", or a nested path such as
|
||||
* "project.name", or any
|
||||
* <a href="https://github.com/jayway/JsonPath">JsonPath</a>.
|
||||
*
|
||||
* @param path the path to switch to
|
||||
* @return spec for asserting the content under the given path
|
||||
* @throws AssertionError if the GraphQL response contains
|
||||
* <a href="https://spec.graphql.org/June2018/#sec-Errors">errors</a>
|
||||
* that have not be checked via {@link ResponseSpec#errors()}
|
||||
* <a href="https://spec.graphql.org/June2018/#sec-Errors">errors</a> that have
|
||||
* not be checked via {@link ResponseSpec#errors()}
|
||||
*/
|
||||
PathSpec path(String path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare options to check the data and errors of a GraphQL response.
|
||||
@@ -206,15 +210,15 @@ public interface GraphQlTester {
|
||||
interface ResponseSpec extends TraverseSpec {
|
||||
|
||||
/**
|
||||
* Return a spec to filter out or inspect errors. This must be used
|
||||
* before traversing to a {@link #path(String)} if some errors are
|
||||
* expected and need to be filtered out.
|
||||
* Return a spec to filter out or inspect errors. This must be used before
|
||||
* traversing to a {@link #path(String)} if some errors are expected and need to
|
||||
* be filtered out.
|
||||
* @return the error spec
|
||||
*/
|
||||
ErrorSpec errors();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options available to assert data at a given path.
|
||||
*/
|
||||
@@ -233,8 +237,8 @@ public interface GraphQlTester {
|
||||
PathSpec pathDoesNotExist();
|
||||
|
||||
/**
|
||||
* Assert a value exists at the given path where the value is any
|
||||
* {@code non-null} value, possibly an empty array or map.
|
||||
* Assert a value exists at the given path where the value is any {@code non-null}
|
||||
* value, possibly an empty array or map.
|
||||
* @return spec to assert the converted entity with
|
||||
*/
|
||||
PathSpec valueExists();
|
||||
@@ -246,22 +250,22 @@ public interface GraphQlTester {
|
||||
PathSpec valueDoesNotExist();
|
||||
|
||||
/**
|
||||
* Assert the value at the given path does not exist or is empty as defined
|
||||
* in {@link org.springframework.util.ObjectUtils#isEmpty(Object)}.
|
||||
* Assert the value at the given path does not exist or is empty as defined in
|
||||
* {@link org.springframework.util.ObjectUtils#isEmpty(Object)}.
|
||||
* @return spec to assert the converted entity with
|
||||
* @see org.springframework.util.ObjectUtils#isEmpty(Object)
|
||||
*/
|
||||
PathSpec valueIsEmpty();
|
||||
|
||||
/**
|
||||
* Assert the value at the given path is not {@link #valueIsEmpty()}
|
||||
* Assert the value at the given path is not {@link #valueIsEmpty()}.
|
||||
* @return spec to assert the converted entity with
|
||||
*/
|
||||
PathSpec valueIsNotEmpty();
|
||||
|
||||
/**
|
||||
* Convert the data at the given path to the target type.
|
||||
* @param entityType the type to convert to
|
||||
* @param entityType the type to convert to
|
||||
* @param <D> the target entity type
|
||||
* @return spec to assert the converted entity with
|
||||
*/
|
||||
@@ -292,33 +296,36 @@ public interface GraphQlTester {
|
||||
<D> ListEntitySpec<D> entityList(ParameterizedTypeReference<D> elementType);
|
||||
|
||||
/**
|
||||
* Parse the JSON at the given path and the given expected JSON and assert
|
||||
* that the two are "similar".
|
||||
* <p>Use of this option requires the
|
||||
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library
|
||||
* on to be on the classpath.
|
||||
* Parse the JSON at the given path and the given expected JSON and assert that
|
||||
* the two are "similar".
|
||||
* <p>
|
||||
* Use of this option requires the
|
||||
* <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library on to be
|
||||
* on the classpath.
|
||||
* @param expectedJson the expected JSON
|
||||
* @return spec to specify a different path
|
||||
* @see org.springframework.test.util.JsonExpectationsHelper#assertJsonEqual(String, String)
|
||||
* @see org.springframework.test.util.JsonExpectationsHelper#assertJsonEqual(String,
|
||||
* String)
|
||||
*/
|
||||
TraverseSpec matchesJson(String expectedJson);
|
||||
|
||||
/**
|
||||
* Parse the JSON at the given path and the given expected JSON and assert
|
||||
* that the two are "similar" so they contain the same attribute-value
|
||||
* pairs regardless of formatting, along with lenient checking, e.g.
|
||||
* extensible and non-strict array ordering.
|
||||
* Parse the JSON at the given path and the given expected JSON and assert that
|
||||
* the two are "similar" so they contain the same attribute-value pairs regardless
|
||||
* of formatting, along with lenient checking, e.g. extensible and non-strict
|
||||
* array ordering.
|
||||
* @param expectedJson the expected JSON
|
||||
* @return spec to specify a different path
|
||||
* @see org.springframework.test.util.JsonExpectationsHelper#assertJsonEqual(String, String, boolean)
|
||||
* @see org.springframework.test.util.JsonExpectationsHelper#assertJsonEqual(String,
|
||||
* String, boolean)
|
||||
*/
|
||||
TraverseSpec matchesJsonStrictly(String expectedJson);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options available to assert data converted to an entity.
|
||||
*
|
||||
* @param <D> the entity type
|
||||
* @param <S> the spec type, including subtypes
|
||||
*/
|
||||
@@ -367,21 +374,23 @@ public interface GraphQlTester {
|
||||
/**
|
||||
* Perform any assertions on the converted entity, e.g. via AssertJ.
|
||||
* @param consumer the consumer to inspect the entity with
|
||||
* @param <T> the spec type
|
||||
* @return the same spec for more assertions
|
||||
*/
|
||||
<T extends S> T satisfies(Consumer<D> consumer);
|
||||
|
||||
/**
|
||||
* Return the converted entity.
|
||||
* @return the converter entity
|
||||
*/
|
||||
D get();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension of {@link EntitySpec} with options available to assert data
|
||||
* converted to a List of entities.
|
||||
* Extension of {@link EntitySpec} with options available to assert data converted to
|
||||
* a List of entities.
|
||||
*
|
||||
* @param <E> the type of elements in the list
|
||||
*/
|
||||
interface ListEntitySpec<E> extends EntitySpec<List<E>, ListEntitySpec<E>> {
|
||||
@@ -433,16 +442,15 @@ public interface GraphQlTester {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options to filter out expected errors or inspect all errors and
|
||||
* verify there are no unexpected errors.
|
||||
* Declare options to filter out expected errors or inspect all errors and verify
|
||||
* there are no unexpected errors.
|
||||
*/
|
||||
interface ErrorSpec {
|
||||
|
||||
/**
|
||||
* Add a filter for expected errors. All errors that match the predicate
|
||||
* are treated as expected and ignored on {@link #verify()} or when
|
||||
* Add a filter for expected errors. All errors that match the predicate are
|
||||
* treated as expected and ignored on {@link #verify()} or when
|
||||
* {@link TraverseSpec#path(String) traversing} to a data path.
|
||||
* @param errorPredicate the predicate to add
|
||||
* @return the same spec to add more filters before {@link #verify()}
|
||||
@@ -450,17 +458,16 @@ public interface GraphQlTester {
|
||||
ErrorSpec filter(Predicate<GraphQLError> errorPredicate);
|
||||
|
||||
/**
|
||||
* Verify there are either no errors or that there no unexpected errors
|
||||
* that have not been {@link #filter(Predicate) filtered out}.
|
||||
* Verify there are either no errors or that there no unexpected errors that have
|
||||
* not been {@link #filter(Predicate) filtered out}.
|
||||
* @return a spec to switch to a data path
|
||||
*/
|
||||
TraverseSpec verify();
|
||||
|
||||
/**
|
||||
* Inspect <a href="https://spec.graphql.org/June2018/#sec-Errors">errors</a>
|
||||
* in the response, if any. Use of this method effectively suppresses
|
||||
* all errors and allows {@link TraverseSpec#path(String) traversing} to a
|
||||
* data path.
|
||||
* Inspect <a href="https://spec.graphql.org/June2018/#sec-Errors">errors</a> in
|
||||
* the response, if any. Use of this method effectively suppresses all errors and
|
||||
* allows {@link TraverseSpec#path(String) traversing} to a data path.
|
||||
* @param errorsConsumer to inspect errors with
|
||||
* @return a spec to switch to a data path
|
||||
*/
|
||||
@@ -468,30 +475,29 @@ public interface GraphQlTester {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare options available to assert a GraphQL Subscription response.
|
||||
*/
|
||||
interface SubscriptionSpec {
|
||||
|
||||
/**
|
||||
* Return a {@link Flux} of entities converted from some part of the data
|
||||
* in each subscription event.
|
||||
* Return a {@link Flux} of entities converted from some part of the data in each
|
||||
* subscription event.
|
||||
* @param path a path into the data of each subscription event
|
||||
* @param entityType the type to convert data to
|
||||
* @param <T> the entity type
|
||||
* @return a {@code Flux} of entities that can be further inspected,
|
||||
* e.g. with {@code reactor.test.StepVerifier}
|
||||
* @return a {@code Flux} of entities that can be further inspected, e.g. with
|
||||
* {@code reactor.test.StepVerifier}
|
||||
*/
|
||||
default <T> Flux<T> toFlux(String path, Class<T> entityType) {
|
||||
return toFlux().map(spec -> spec.path(path).entity(entityType).get());
|
||||
return toFlux().map((spec) -> spec.path(path).entity(entityType).get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Flux} of {@link ResponseSpec} instances, each
|
||||
* representing an individual subscription event.
|
||||
* @return a {@code Flux} of {@code ResponseSpec} instances that can be
|
||||
* further inspected, e.g. with {@code reactor.test.StepVerifier}
|
||||
* Return a {@link Flux} of {@link ResponseSpec} instances, each representing an
|
||||
* individual subscription event.
|
||||
* @return a {@code Flux} of {@code ResponseSpec} instances that can be further
|
||||
* inspected, e.g. with {@code reactor.test.StepVerifier}
|
||||
*/
|
||||
Flux<ResponseSpec> toFlux();
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -27,6 +28,9 @@ import graphql.GraphQLError;
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} with setters for deserialization.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class TestExecutionResult implements ExecutionResult {
|
||||
|
||||
@@ -36,7 +40,6 @@ public class TestExecutionResult implements ExecutionResult {
|
||||
|
||||
private Map<Object, Object> extensions = Collections.emptyMap();
|
||||
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
@@ -72,8 +75,7 @@ public class TestExecutionResult implements ExecutionResult {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toSpecification() {
|
||||
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult()
|
||||
.addErrors(this.errors)
|
||||
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult().addErrors(this.errors)
|
||||
.extensions(this.extensions);
|
||||
|
||||
if (isDataPresent()) {
|
||||
@@ -82,4 +84,5 @@ public class TestExecutionResult implements ExecutionResult {
|
||||
|
||||
return builder.build().toSpecification();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.List;
|
||||
@@ -28,6 +29,8 @@ import graphql.language.SourceLocation;
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} with setters to use for deserialization.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
class TestGraphQlError implements GraphQLError {
|
||||
|
||||
@@ -43,9 +46,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
|
||||
private boolean expected;
|
||||
|
||||
|
||||
|
||||
public void setMessage(String message) {
|
||||
void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setLocations(List<TestSourceLocation> locations) {
|
||||
void setLocations(List<TestSourceLocation> locations) {
|
||||
this.locations = TestSourceLocation.toSourceLocations(locations);
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
return this.locations;
|
||||
}
|
||||
|
||||
public void setErrorType(ErrorClassification errorType) {
|
||||
void setErrorType(ErrorClassification errorType) {
|
||||
this.errorType = errorType;
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
return this.errorType;
|
||||
}
|
||||
|
||||
public void setPath(List<Object> path) {
|
||||
void setPath(List<Object> path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setExtensions(Map<String, Object> extensions) {
|
||||
void setExtensions(Map<String, Object> extensions) {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
@@ -92,8 +93,9 @@ class TestGraphQlError implements GraphQLError {
|
||||
|
||||
/**
|
||||
* Whether the error is marked as filtered out as expected.
|
||||
* @return whether the error is marked as expected
|
||||
*/
|
||||
public boolean isExpected() {
|
||||
boolean isExpected() {
|
||||
return this.expected;
|
||||
}
|
||||
|
||||
@@ -117,6 +119,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
|
||||
/**
|
||||
* Mark this error as expected if it matches the predicate.
|
||||
* @param predicate the error predicate
|
||||
*/
|
||||
void filter(Predicate<GraphQLError> predicate) {
|
||||
this.expected |= predicate.test(this);
|
||||
@@ -127,8 +130,7 @@ class TestGraphQlError implements GraphQLError {
|
||||
return toSpecification().toString();
|
||||
}
|
||||
|
||||
|
||||
private static class TestSourceLocation {
|
||||
private static final class TestSourceLocation {
|
||||
|
||||
private int line;
|
||||
|
||||
@@ -136,35 +138,36 @@ class TestGraphQlError implements GraphQLError {
|
||||
|
||||
private String sourceName;
|
||||
|
||||
|
||||
public void setLine(int line) {
|
||||
void setLine(int line) {
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
int getLine() {
|
||||
return this.line;
|
||||
}
|
||||
|
||||
public void setColumn(int column) {
|
||||
void setColumn(int column) {
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public int getColumn() {
|
||||
int getColumn() {
|
||||
return this.column;
|
||||
}
|
||||
|
||||
public void setSourceName(String sourceName) {
|
||||
void setSourceName(String sourceName) {
|
||||
this.sourceName = sourceName;
|
||||
}
|
||||
|
||||
public String getSourceName() {
|
||||
String getSourceName() {
|
||||
return this.sourceName;
|
||||
}
|
||||
|
||||
public static List<SourceLocation> toSourceLocations(List<TestSourceLocation> locations) {
|
||||
static List<SourceLocation> toSourceLocations(List<TestSourceLocation> locations) {
|
||||
return locations.stream()
|
||||
.map(location -> new SourceLocation(location.line, location.column, location.sourceName))
|
||||
.map((location) -> new SourceLocation(location.line, location.column, location.sourceName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
@@ -23,14 +24,16 @@ import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
|
||||
/**
|
||||
* {@link TypeRef} with a {@link #getType() type} that is given rather than
|
||||
* obtained from the declared generic type information.
|
||||
* {@link TypeRef} with a {@link #getType() type} that is given rather than obtained from
|
||||
* the declared generic type information.
|
||||
*
|
||||
* @param <T> the referenced type
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
class TypeRefAdapter<T> extends TypeRef<T> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
|
||||
TypeRefAdapter(Class<T> clazz) {
|
||||
this.type = clazz;
|
||||
}
|
||||
@@ -47,7 +50,6 @@ class TypeRefAdapter<T> extends TypeRef<T> {
|
||||
this.type = ResolvableType.forClassWithGenerics(clazz, ResolvableType.forType(generic)).getType();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* GraphQL client testing support.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -51,8 +52,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlTester} parameterized to:
|
||||
@@ -61,20 +62,18 @@ import static org.mockito.Mockito.when;
|
||||
* <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.
|
||||
* <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 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 {
|
||||
@@ -88,7 +87,7 @@ public class GraphQlTesterTests {
|
||||
spec.path("me.friends").valueIsEmpty();
|
||||
spec.path("hero").pathDoesNotExist().valueDoesNotExist().valueIsEmpty();
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -103,13 +102,13 @@ public class GraphQlTesterTests {
|
||||
|
||||
spec.path("").matchesJson("{\"me\": {\"name\":\"Luke Skywalker\",\"friends\":[]}}");
|
||||
spec.path("me").matchesJson("{\"name\":\"Luke Skywalker\"}");
|
||||
spec.path("me").matchesJson("{\"friends\":[]}"); // lenient match with subset of fields
|
||||
spec.path("me").matchesJson("{\"friends\":[]}"); // lenient match with subset of
|
||||
// fields
|
||||
|
||||
assertThatThrownBy(() -> spec.path("me").matchesJsonStrictly("{\"friends\":[]}"))
|
||||
.as("Extended fields should fail in strict mode")
|
||||
.hasMessageContaining("Unexpected: name");
|
||||
.as("Extended fields should fail in strict mode").hasMessageContaining("Unexpected: name");
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -126,22 +125,16 @@ public class GraphQlTesterTests {
|
||||
MovieCharacter han = MovieCharacter.create("Han Solo");
|
||||
AtomicReference<MovieCharacter> personRef = new AtomicReference<>();
|
||||
|
||||
MovieCharacter actual = spec.path("me").entity(MovieCharacter.class)
|
||||
.isEqualTo(luke)
|
||||
.isNotEqualTo(han)
|
||||
.satisfies(personRef::set)
|
||||
.matches(movieCharacter -> personRef.get().equals(movieCharacter))
|
||||
.isSameAs(personRef.get())
|
||||
.isNotSameAs(luke)
|
||||
.get();
|
||||
MovieCharacter actual = spec.path("me").entity(MovieCharacter.class).isEqualTo(luke).isNotEqualTo(han)
|
||||
.satisfies(personRef::set).matches((movieCharacter) -> personRef.get().equals(movieCharacter))
|
||||
.isSameAs(personRef.get()).isNotSameAs(luke).get();
|
||||
|
||||
assertThat(actual.getName()).isEqualTo("Luke Skywalker");
|
||||
|
||||
spec.path("")
|
||||
.entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() {})
|
||||
.isEqualTo(Collections.singletonMap("me", luke));
|
||||
spec.path("").entity(new ParameterizedTypeReference<Map<String, MovieCharacter>>() {
|
||||
}).isEqualTo(Collections.singletonMap("me", luke));
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -150,13 +143,8 @@ public class GraphQlTesterTests {
|
||||
void entityList(GraphQlTesterSetup setup) throws Exception {
|
||||
|
||||
String query = "{me {name, friends}}";
|
||||
setup.response("{" +
|
||||
" \"me\":{" +
|
||||
" \"name\":\"Luke Skywalker\"," +
|
||||
" \"friends\":[{\"name\":\"Han Solo\"}, {\"name\":\"Leia Organa\"}]" +
|
||||
" }" +
|
||||
"}"
|
||||
);
|
||||
setup.response("{" + " \"me\":{" + " \"name\":\"Luke Skywalker\","
|
||||
+ " \"friends\":[{\"name\":\"Han Solo\"}, {\"name\":\"Leia Organa\"}]" + " }" + "}");
|
||||
|
||||
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query).execute();
|
||||
|
||||
@@ -164,22 +152,16 @@ public class GraphQlTesterTests {
|
||||
MovieCharacter leia = MovieCharacter.create("Leia Organa");
|
||||
MovieCharacter jabba = MovieCharacter.create("Jabba the Hutt");
|
||||
|
||||
List<MovieCharacter> actual = spec.path("me.friends").entityList(MovieCharacter.class)
|
||||
.contains(han)
|
||||
.containsExactly(han, leia)
|
||||
.doesNotContain(jabba)
|
||||
.hasSize(2)
|
||||
.hasSizeGreaterThan(1)
|
||||
.hasSizeLessThan(3)
|
||||
List<MovieCharacter> actual = spec.path("me.friends").entityList(MovieCharacter.class).contains(han)
|
||||
.containsExactly(han, leia).doesNotContain(jabba).hasSize(2).hasSizeGreaterThan(1).hasSizeLessThan(3)
|
||||
.get();
|
||||
|
||||
assertThat(actual).containsExactly(han, leia);
|
||||
|
||||
spec.path("me.friends")
|
||||
.entityList(new ParameterizedTypeReference<MovieCharacter>() {})
|
||||
.containsExactly(han, leia);
|
||||
spec.path("me.friends").entityList(new ParameterizedTypeReference<MovieCharacter>() {
|
||||
}).containsExactly(han, leia);
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -187,23 +169,17 @@ public class GraphQlTesterTests {
|
||||
@MethodSource("argumentSource")
|
||||
void operationNameAndVariables(GraphQlTesterSetup setup) throws Exception {
|
||||
|
||||
String query = "query HeroNameAndFriends($episode: Episode) {" +
|
||||
" hero(episode: $episode) {" +
|
||||
" name" +
|
||||
" }" +
|
||||
"}";
|
||||
String query = "query HeroNameAndFriends($episode: Episode) {" + " hero(episode: $episode) {" + " name"
|
||||
+ " }" + "}";
|
||||
|
||||
setup.response("{\"hero\": {\"name\":\"R2-D2\"}}");
|
||||
|
||||
GraphQlTester.ResponseSpec spec = setup.graphQlTester().query(query)
|
||||
.operationName("HeroNameAndFriends")
|
||||
.variable("episode", "JEDI")
|
||||
.variables(map -> map.put("foo", "bar"))
|
||||
.execute();
|
||||
GraphQlTester.ResponseSpec spec = setup.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 -> {
|
||||
setup.verifyRequest((input) -> {
|
||||
assertThat(input.getQuery()).contains(query);
|
||||
assertThat(input.getOperationName()).isEqualTo("HeroNameAndFriends");
|
||||
assertThat(input.getVariables()).hasSize(2);
|
||||
@@ -223,7 +199,7 @@ public class GraphQlTesterTests {
|
||||
assertThatThrownBy(() -> setup.graphQlTester().query(query).executeAndVerify())
|
||||
.hasMessageContaining("Response has 1 unexpected error(s).");
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -237,7 +213,7 @@ public class GraphQlTesterTests {
|
||||
assertThatThrownBy(() -> setup.graphQlTester().query(query).execute().path("me"))
|
||||
.hasMessageContaining("Response has 1 unexpected error(s).");
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -246,18 +222,14 @@ public class GraphQlTesterTests {
|
||||
void errorsPartiallyFiltered(GraphQlTesterSetup setup) throws Exception {
|
||||
|
||||
String query = "{me {name, friends}}";
|
||||
setup.response(
|
||||
GraphqlErrorBuilder.newError().message("some error").build(),
|
||||
setup.response(GraphqlErrorBuilder.newError().message("some error").build(),
|
||||
GraphqlErrorBuilder.newError().message("some other error").build());
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
setup.graphQlTester().query(query).execute()
|
||||
.errors()
|
||||
.filter(error -> error.getMessage().equals("some error"))
|
||||
.verify())
|
||||
.hasMessageContaining("Response has 1 unexpected error(s) of 2 total.");
|
||||
assertThatThrownBy(() -> setup.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.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -266,17 +238,13 @@ public class GraphQlTesterTests {
|
||||
void errorsFiltered(GraphQlTesterSetup setup) throws Exception {
|
||||
|
||||
String query = "{me {name, friends}}";
|
||||
setup.response(
|
||||
GraphqlErrorBuilder.newError().message("some error").build(),
|
||||
setup.response(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 "))
|
||||
.verify()
|
||||
.path("me").pathDoesNotExist();
|
||||
setup.graphQlTester().query(query).execute().errors().filter((error) -> error.getMessage().startsWith("some "))
|
||||
.verify().path("me").pathDoesNotExist();
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
@@ -285,26 +253,21 @@ public class GraphQlTesterTests {
|
||||
void errorsConsumed(GraphQlTesterSetup setup) throws Exception {
|
||||
|
||||
String query = "{me {name, friends}}";
|
||||
setup.response(GraphqlErrorBuilder.newError()
|
||||
.message("Invalid query")
|
||||
.location(new SourceLocation(1, 2))
|
||||
.build());
|
||||
setup.response(
|
||||
GraphqlErrorBuilder.newError().message("Invalid query").location(new SourceLocation(1, 2)).build());
|
||||
|
||||
setup.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);
|
||||
assertThat(errors.get(0).getLocations().get(0).getLine()).isEqualTo(1);
|
||||
assertThat(errors.get(0).getLocations().get(0).getColumn()).isEqualTo(2);
|
||||
})
|
||||
.path("me").pathDoesNotExist();
|
||||
setup.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);
|
||||
assertThat(errors.get(0).getLocations().get(0).getLine()).isEqualTo(1);
|
||||
assertThat(errors.get(0).getLocations().get(0).getColumn()).isEqualTo(2);
|
||||
}).path("me").pathDoesNotExist();
|
||||
|
||||
setup.verifyRequest(input -> assertThat(input.getQuery()).contains(query));
|
||||
setup.verifyRequest((input) -> assertThat(input.getQuery()).contains(query));
|
||||
setup.shutdown();
|
||||
}
|
||||
|
||||
|
||||
private interface GraphQlTesterSetup {
|
||||
|
||||
GraphQlTester graphQlTester();
|
||||
@@ -327,7 +290,6 @@ public class GraphQlTesterTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class MockWebServerSetup implements GraphQlTesterSetup {
|
||||
|
||||
private final MockWebServer server;
|
||||
@@ -356,12 +318,10 @@ public class GraphQlTesterTests {
|
||||
sb.append("\"data\":").append(data);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(errors)) {
|
||||
List<Map<String, Object>> errorSpecs = errors.stream()
|
||||
.map(GraphQLError::toSpecification)
|
||||
List<Map<String, Object>> errorSpecs = errors.stream().map(GraphQLError::toSpecification)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
sb.append(StringUtils.hasText(data) ? ", " : "")
|
||||
.append("\"errors\":")
|
||||
sb.append(StringUtils.hasText(data) ? ", " : "").append("\"errors\":")
|
||||
.append(OBJECT_MAPPER.writeValueAsString(errorSpecs));
|
||||
}
|
||||
sb.append("}");
|
||||
@@ -380,7 +340,8 @@ public class GraphQlTesterTests {
|
||||
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>>() {});
|
||||
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);
|
||||
@@ -390,8 +351,8 @@ public class GraphQlTesterTests {
|
||||
public void shutdown() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockWebGraphQlHandlerSetup implements GraphQlTesterSetup {
|
||||
|
||||
@@ -401,7 +362,7 @@ public class GraphQlTesterTests {
|
||||
|
||||
private final GraphQlTester graphQlTester;
|
||||
|
||||
public MockWebGraphQlHandlerSetup() {
|
||||
MockWebGraphQlHandlerSetup() {
|
||||
this.graphQlTester = GraphQlTester.create(this.handler);
|
||||
}
|
||||
|
||||
@@ -414,14 +375,15 @@ public class GraphQlTesterTests {
|
||||
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>>() {}));
|
||||
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);
|
||||
when(this.handler.handle(this.bodyCaptor.capture())).thenReturn(Mono.just(output));
|
||||
given(this.handler.handle(this.bodyCaptor.capture())).willReturn(Mono.just(output));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -429,6 +391,7 @@ public class GraphQlTesterTests {
|
||||
WebInput webInput = this.bodyCaptor.getValue();
|
||||
consumer.accept(webInput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -46,11 +47,12 @@ public class MovieCharacter {
|
||||
return false;
|
||||
}
|
||||
MovieCharacter movieCharacter = (MovieCharacter) other;
|
||||
return (this.name != null ? this.name.equals(movieCharacter.name) : movieCharacter.name == null);
|
||||
return (this.name != null) ? this.name.equals(movieCharacter.name) : movieCharacter.name == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.name != null ? this.name.hashCode() : 0);
|
||||
return (this.name != null) ? this.name.hashCode() : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user