Refactoring in spring-graphql tests

Change GraphQlTestUtils to match how tests have evolved with regards to
how GraphQlSource is initialized and ensure all tests use it.
This commit is contained in:
Rossen Stoyanchev
2021-11-04 15:47:51 +00:00
parent 2e820dce7a
commit 3318e81885
18 changed files with 331 additions and 396 deletions

View File

@@ -24,8 +24,14 @@ import java.util.stream.Collectors;
import reactor.core.publisher.Flux;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class BookSource {
public static final Resource schema = new ClassPathResource("books/schema.graphqls");
private static final Map<Long, Book> booksMap = new HashMap<>();
private static final Map<Long, Book> booksWithoutAuthorsMap;

View File

@@ -17,16 +17,15 @@
package org.springframework.graphql;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.DataFetcher;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.core.io.Resource;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,39 +35,49 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public abstract class GraphQlTestUtils {
public static GraphQL initGraphQl(
String schemaContent, String typeName, String fieldName, DataFetcher<?> fetcher) {
/**
* Convenience method for a {@link GraphQlSource.Builder} with a single {@link DataFetcher}.
*
* @param schema either String content or a {@link Resource}.
* @param typeName the parent type name (Query, Mutation, or Subscription).
* @param fieldName the name of the operation
* @param fetcher the fetcher to use
*
* @return the created builder
*/
public static GraphQlSource.Builder graphQlSource(
Object schema, String typeName, String fieldName, DataFetcher<?> fetcher) {
return initGraphQlSource(schemaContent, typeName, fieldName, fetcher)
.build()
.graphQl();
return graphQlSource(schema,
wiring -> wiring.type(typeName, builder -> builder.dataFetcher(fieldName, fetcher)));
}
public static GraphQL initGraphQl(
String schemaContent, String typeName, String fieldName, DataFetcher<?> fetcher,
DataFetcherExceptionResolver... resolvers) {
return initGraphQlSource(schemaContent, typeName, fieldName, fetcher)
.exceptionResolvers(Arrays.asList(resolvers))
.build()
.graphQl();
}
public static GraphQlSource.Builder initGraphQlSource(
String schemaContent, String typeName, String fieldName, DataFetcher<?> fetcher) {
/**
* Convenience method for a {@link GraphQlSource.Builder} when multiple
* {@link DataFetcher} registrations might be needed.
*
* @param schema either String content or a {@link Resource}.
* @param configurer the configurer to apply to the RuntimeWiring
*
* @return the created builder
*/
public static GraphQlSource.Builder graphQlSource(Object schema, RuntimeWiringConfigurer configurer) {
Resource schemaResource = (schema instanceof String ?
new ByteArrayResource(((String) schema).getBytes(StandardCharsets.UTF_8)) :
(Resource) schema);
return GraphQlSource.builder()
.schemaResources(new ByteArrayResource(schemaContent.getBytes(StandardCharsets.UTF_8)))
.configureRuntimeWiring(wiring -> wiring.type(typeName, (builder) -> builder.dataFetcher(fieldName, fetcher)));
.schemaResources(schemaResource)
.configureRuntimeWiring(configurer);
}
@SuppressWarnings("unchecked")
public static <T> T checkErrorsAndGetData(@Nullable ExecutionResult result, String key) {
Map<String, Object> map = checkErrorsAndGetData(result);
public static <T> T getData(@Nullable ExecutionResult result, String key) {
Map<String, Object> map = getData(result);
return (T) map.get(key);
}
public static <T> T checkErrorsAndGetData(@Nullable ExecutionResult result) {
public static <T> T getData(@Nullable ExecutionResult result) {
assertThat(result).isNotNull();
assertThat(result.getErrors()).as("Errors present in GraphQL response").isEmpty();
T data = result.getData();

View File

@@ -1,45 +0,0 @@
/*
* 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;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import org.springframework.graphql.execution.GraphQlSource;
/**
* {@link GraphQlSource} that wraps a pre-built {@link GraphQL} instance.
*/
public class TestGraphQlSource implements GraphQlSource {
private final GraphQL graphQl;
public TestGraphQlSource(GraphQL graphQl) {
this.graphQl = graphQl;
}
@Override
public GraphQL graphQl() {
return this.graphQl;
}
@Override
public GraphQLSchema schema() {
throw new UnsupportedOperationException();
}
}

View File

@@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.graphql.Book;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
@@ -43,7 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ArgumentMethodArgumentResolver}.
*
* @author Brian Clozel
*/
class ArgumentMethodArgumentResolverTests {
@@ -55,24 +53,25 @@ class ArgumentMethodArgumentResolverTests {
@Test
void shouldSupportAnnotatedParameters() {
Method bookById = ClassUtils.getMethod(BookController.class, "bookById", Long.class);
MethodParameter methodParameter = getMethodParameter(bookById, 0);
MethodParameter methodParameter = methodParam(bookById, 0);
assertThat(resolver.supportsParameter(methodParameter)).isTrue();
}
@Test
void shouldNotSupportParametersWithoutAnnotation() {
Method notSupported = ClassUtils.getMethod(BookController.class, "notSupported", String.class);
MethodParameter methodParameter = getMethodParameter(notSupported, 0);
MethodParameter methodParameter = methodParam(notSupported, 0);
assertThat(resolver.supportsParameter(methodParameter)).isFalse();
}
@Test
void shouldResolveBasicTypeArgument() throws Exception {
Method bookById = ClassUtils.getMethod(BookController.class, "bookById", Long.class);
String payload = "{\"id\": 42 }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(bookById, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
DataFetchingEnvironment environment = initEnvironment("{\"id\": 42 }");
Object result = resolver.resolveArgument(methodParam(bookById, 0), environment);
assertThat(result).isNotNull().isInstanceOf(Long.class).isEqualTo(42L);
}
@@ -81,8 +80,8 @@ class ArgumentMethodArgumentResolverTests {
Method addBook = ClassUtils.getMethod(BookController.class, "addBook", BookInput.class);
String payload = "{\"bookInput\": { \"name\": \"test name\", \"authorId\": 42} }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(addBook, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
Object result = resolver.resolveArgument(methodParam(addBook, 0), environment);
assertThat(result).isNotNull().isInstanceOf(BookInput.class);
assertThat((BookInput) result).hasFieldOrPropertyWithValue("name", "test name")
.hasFieldOrPropertyWithValue("authorId", 42L);
@@ -93,8 +92,8 @@ class ArgumentMethodArgumentResolverTests {
Method addBooks = ClassUtils.getMethod(BookController.class, "addBooks", List.class);
String payload = "{\"books\": [{ \"name\": \"first\", \"authorId\": 42}, { \"name\": \"second\", \"authorId\": 24}] }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(addBooks, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
Object result = resolver.resolveArgument(methodParam(addBooks, 0), environment);
assertThat(result).isNotNull().isInstanceOf(List.class);
assertThat(result).asList().allMatch(item -> item instanceof Book)
.extracting("name").containsExactly("first", "second");
@@ -105,13 +104,13 @@ class ArgumentMethodArgumentResolverTests {
Method bookByKeyword = ClassUtils.getMethod(BookController.class, "bookByKeyword", Keyword.class);
String payload = "{\"keyword\": \"test\" }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(bookByKeyword, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
Object result = resolver.resolveArgument(methodParam(bookByKeyword, 0), environment);
assertThat(result).isNotNull().isInstanceOf(Keyword.class);
assertThat((Keyword) result).hasFieldOrPropertyWithValue("term", "test");
}
private MethodParameter getMethodParameter(Method method, int index) {
private MethodParameter methodParam(Method method, int index) {
MethodParameter methodParameter = new MethodParameter(method, index);
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
return methodParameter;
@@ -122,6 +121,7 @@ class ArgumentMethodArgumentResolverTests {
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment().arguments(arguments).build();
}
@Controller
static class BookController {

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.graphql.data.method.annotation.support;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -36,7 +35,6 @@ import reactor.core.publisher.Mono;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.RequestInput;
@@ -121,7 +119,7 @@ public class BatchMappingInvocationTests {
.execute(new RequestInput(query, null, null, null))
.block();
List<Map<String, Object>> actualCourses = GraphQlTestUtils.checkErrorsAndGetData(result, "courses");
List<Map<String, Object>> actualCourses = GraphQlTestUtils.getData(result, "courses");
List<Course> courses = Course.allCourses();
assertThat(actualCourses).hasSize(courses.size());
@@ -153,7 +151,7 @@ public class BatchMappingInvocationTests {
.execute(new RequestInput(query, null, null, null))
.block();
List<Map<String, Object>> actualCourses = GraphQlTestUtils.checkErrorsAndGetData(result, "courses");
List<Map<String, Object>> actualCourses = GraphQlTestUtils.getData(result, "courses");
List<Course> courses = Course.allCourses();
assertThat(actualCourses).hasSize(courses.size());
@@ -253,15 +251,8 @@ public class BatchMappingInvocationTests {
private static class CourseConfig {
@Bean
public GraphQlSource graphQlSource(AnnotatedControllerConfigurer configurer) {
return GraphQlSource.builder()
.schemaResources(new ByteArrayResource(schema.getBytes(StandardCharsets.UTF_8)))
.configureRuntimeWiring(configurer)
.build();
}
@Bean
public GraphQlService graphQlService(GraphQlSource source, BatchLoaderRegistry registry) {
public GraphQlService graphQlService(AnnotatedControllerConfigurer configurer, BatchLoaderRegistry registry) {
GraphQlSource source = GraphQlTestUtils.graphQlSource(schema, configurer).build();
ExecutionGraphQlService service = new ExecutionGraphQlService(source);
service.addDataLoaderRegistrar(registry);
return service;

View File

@@ -57,8 +57,7 @@ public class DataFetchingEnvironmentArgumentResolverTests {
@Test
void resolveGraphQlContext() {
GraphQLContext context = GraphQLContext.newContext().build();
DataFetchingEnvironment environment =
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().graphQLContext(context).build();
DataFetchingEnvironment environment = environment().graphQLContext(context).build();
Object actual = this.resolver.resolveArgument(parameter(0), environment);
assertThat(actual).isSameAs(context);
@@ -67,8 +66,7 @@ public class DataFetchingEnvironmentArgumentResolverTests {
@Test
void resolveSelectionSet() {
DataFetchingFieldSelectionSet selectionSet = mock(DataFetchingFieldSelectionSet.class);
DataFetchingEnvironment environment =
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().selectionSet(selectionSet).build();
DataFetchingEnvironment environment = environment().selectionSet(selectionSet).build();
Object actual = this.resolver.resolveArgument(parameter(1), environment);
assertThat(actual).isSameAs(selectionSet);
@@ -77,18 +75,17 @@ public class DataFetchingEnvironmentArgumentResolverTests {
@Test
void resolveLocale() {
Locale locale = Locale.ITALIAN;
DataFetchingEnvironment environment =
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().locale(locale).build();
DataFetchingEnvironment environment = environment().locale(locale).build();
Object actual = this.resolver.resolveArgument(parameter(2), environment);
assertThat(actual).isSameAs(locale);
}
@SuppressWarnings("unchecked")
@Test
void resolveOptionalLocale() {
Locale locale = Locale.ITALIAN;
DataFetchingEnvironment environment =
DataFetchingEnvironmentImpl.newDataFetchingEnvironment().locale(locale).build();
DataFetchingEnvironment environment = environment().locale(locale).build();
Optional<Locale> actual = (Optional<Locale>) this.resolver.resolveArgument(parameter(3), environment);
assertThat(actual).isNotNull();
@@ -96,14 +93,22 @@ public class DataFetchingEnvironmentArgumentResolverTests {
assertThat(actual.get()).isSameAs(locale);
}
private static DataFetchingEnvironmentImpl.Builder environment() {
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment();
}
private MethodParameter parameter(int index) {
return new MethodParameter(handleMethod, index);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public void handle(GraphQLContext graphQLContext, DataFetchingFieldSelectionSet selectionSet,
Locale locale, Optional<Locale> optionalLocale, String s) {
@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"})
public void handle(
GraphQLContext graphQLContext,
DataFetchingFieldSelectionSet selectionSet,
Locale locale,
Optional<Locale> optionalLocale,
String s) {
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.graphql.data.method.annotation.support;
import java.lang.reflect.Method;
import java.util.function.Consumer;
import graphql.GraphQLContext;
import graphql.schema.DataFetchingEnvironment;
@@ -38,7 +37,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Unit tests for {@link DataLoaderMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class DataLoaderArgumentResolverTests {
@@ -48,6 +46,8 @@ public class DataLoaderArgumentResolverTests {
private final DataLoaderMethodArgumentResolver resolver = new DataLoaderMethodArgumentResolver();
private final BatchLoaderRegistry registry = new DefaultBatchLoaderRegistry();
@Test
void supportsParameter() {
@@ -57,70 +57,60 @@ public class DataLoaderArgumentResolverTests {
@Test
void resolveArgument() {
this.registry.forTypePair(Long.class, Author.class).registerBatchLoader((ids, env) -> Flux.empty());
DataFetchingEnvironment environment = initEnvironment(registry ->
registry.forTypePair(Long.class, Author.class).registerBatchLoader((ids, env) -> Flux.empty()));
Object argument = this.resolver.resolveArgument(initParameter(0), environment);
Object argument = this.resolver.resolveArgument(initParameter(0), environment());
assertThat(argument).isNotNull();
}
@Test
void resolveArgumentViaParameterName() {
DataFetchingEnvironment environment = initEnvironment(registry ->
registry.forName("namedDataLoader").registerBatchLoader((ids, env) -> Flux.empty()));
this.registry.forName("namedDataLoader").registerBatchLoader((ids, env) -> Flux.empty());
MethodParameter parameter = initParameter(1);
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
Object argument = this.resolver.resolveArgument(parameter, environment);
Object argument = this.resolver.resolveArgument(parameter, environment());
assertThat(argument).isNotNull();
}
@Test
void resolveArgumentFailureWithoutGenericType() {
DataFetchingEnvironment environment = initEnvironment(registry ->
registry.forTypePair(Long.class, Author.class).registerBatchLoader((ids, env) -> Flux.empty()));
this.registry.forTypePair(Long.class, Author.class).registerBatchLoader((ids, env) -> Flux.empty());
assertThatThrownBy(() -> this.resolver.resolveArgument(initParameter(2), environment))
assertThatThrownBy(() -> this.resolver.resolveArgument(initParameter(2), environment()))
.hasMessageContaining("declaring the DataLoader argument with generic types should help");
}
@Test
void resolveArgumentFailureWithoutParameterName() {
DataFetchingEnvironment environment = initEnvironment(registry ->
registry.forName("namedDataLoader").registerBatchLoader((ids, env) -> Flux.empty()));
this.registry.forName("namedDataLoader").registerBatchLoader((ids, env) -> Flux.empty());
MethodParameter parameter = initParameter(1);
// Skip ParameterNameDiscovery
assertThatThrownBy(() -> this.resolver.resolveArgument(parameter, environment))
assertThatThrownBy(() -> this.resolver.resolveArgument(parameter, environment()))
.hasMessageContaining("compiling with \"-parameters\" should help");
}
@Test
void resolveArgumentFailureNoMatch() {
DataFetchingEnvironment environment = initEnvironment(registry ->
registry.forName("bookDataLoader").registerBatchLoader((ids, env) -> Flux.empty()));
this.registry.forName("bookDataLoader").registerBatchLoader((ids, env) -> Flux.empty());
MethodParameter parameter = initParameter(0);
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
assertThatThrownBy(() -> this.resolver.resolveArgument(parameter, environment))
assertThatThrownBy(() -> this.resolver.resolveArgument(parameter, environment()))
.hasMessageContaining(
"Neither the name of the declared value type 'class org.springframework.graphql.Author' " +
"nor the method parameter name 'authorDataLoader' match to any DataLoader. " +
"The DataLoaderRegistry contains: [bookDataLoader]");
}
private DataFetchingEnvironment initEnvironment(Consumer<BatchLoaderRegistry> registryConsumer) {
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
registryConsumer.accept(batchLoaderRegistry);
DataLoaderRegistry registry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(registry, GraphQLContext.newContext().build());
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment().dataLoaderRegistry(registry).build();
private DataFetchingEnvironment environment() {
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
this.registry.registerDataLoaders(dataLoaderRegistry, GraphQLContext.newContext().build());
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment().dataLoaderRegistry(dataLoaderRegistry).build();
}
private MethodParameter initParameter(int index) {

View File

@@ -32,7 +32,6 @@ import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookCriteria;
@@ -77,7 +76,7 @@ public class SchemaMappingInvocationTests {
.execute(new RequestInput(query, null, null, null))
.block();
Map<String, Object> book = GraphQlTestUtils.checkErrorsAndGetData(result, "bookById");
Map<String, Object> book = GraphQlTestUtils.getData(result, "bookById");
assertThat(book.get("id")).isEqualTo("1");
assertThat(book.get("name")).isEqualTo("Nineteen Eighty-Four");
@@ -99,7 +98,7 @@ public class SchemaMappingInvocationTests {
.execute(new RequestInput(query, null, null, null))
.block();
List<Map<String, Object>> bookList = GraphQlTestUtils.checkErrorsAndGetData(result, "booksByCriteria");
List<Map<String, Object>> bookList = GraphQlTestUtils.getData(result, "booksByCriteria");
assertThat(bookList).hasSize(2);
assertThat(bookList.get(0).get("name")).isEqualTo("Nineteen Eighty-Four");
@@ -127,7 +126,7 @@ public class SchemaMappingInvocationTests {
.execute(requestInput)
.block();
Map<String, Object> author = GraphQlTestUtils.checkErrorsAndGetData(result, "authorById");
Map<String, Object> author = GraphQlTestUtils.getData(result, "authorById");
assertThat(author.get("id")).isEqualTo("101");
assertThat(author.get("firstName")).isEqualTo("George");
@@ -150,7 +149,7 @@ public class SchemaMappingInvocationTests {
.execute(new RequestInput(operation, null, null, null))
.block();
Map<String, Object> author = GraphQlTestUtils.checkErrorsAndGetData(result, "addAuthor");
Map<String, Object> author = GraphQlTestUtils.getData(result, "addAuthor");
assertThat(author.get("id")).isEqualTo("99");
assertThat(author.get("firstName")).isEqualTo("James");
assertThat(author.get("lastName")).isEqualTo("Joyce");
@@ -169,10 +168,10 @@ public class SchemaMappingInvocationTests {
.execute(new RequestInput(operation, null, null, null))
.block();
Publisher<ExecutionResult> publisher = GraphQlTestUtils.checkErrorsAndGetData(result);
Publisher<ExecutionResult> publisher = GraphQlTestUtils.getData(result);
Flux<Map<String, Object>> bookFlux = Flux.from(publisher).map(rs -> {
Map<String, Object> map = rs.getData();
Flux<Map<String, Object>> bookFlux = Flux.from(publisher).map(executionResult -> {
Map<String, Object> map = executionResult.getData();
return (Map<String, Object>) map.get("bookSearch");
});
@@ -207,22 +206,15 @@ public class SchemaMappingInvocationTests {
}
@Bean
public GraphQlService graphQlService(GraphQlSource graphQlSource) {
ExecutionGraphQlService service = new ExecutionGraphQlService(graphQlSource);
public GraphQlService graphQlService(AnnotatedControllerConfigurer configurer) {
GraphQlSource source = GraphQlTestUtils.graphQlSource(BookSource.schema, configurer).build();
ExecutionGraphQlService service = new ExecutionGraphQlService(source);
service.addDataLoaderRegistrar(batchLoaderRegistry());
return service;
}
@Bean
public GraphQlSource graphQlSource() {
return GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.configureRuntimeWiring(annotatedDataFetcherConfigurer())
.build();
}
@Bean
public AnnotatedControllerConfigurer annotatedDataFetcherConfigurer() {
public AnnotatedControllerConfigurer annotatedControllerConfigurer() {
return new AnnotatedControllerConfigurer();
}

View File

@@ -19,20 +19,20 @@ package org.springframework.graphql.data.querydsl;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.querydsl.core.types.Predicate;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLTypeVisitor;
import graphql.schema.idl.TypeRuntimeWiring;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.keyvalue.core.KeyValueTemplate;
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
import org.springframework.data.map.MapKeyValueAdapter;
@@ -42,9 +42,12 @@ import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.graphql.Author;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.data.GraphQlRepository;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInput;
import org.springframework.graphql.web.WebOutput;
@@ -62,32 +65,29 @@ import static org.mockito.Mockito.when;
*/
class QuerydslDataFetcherTests {
private KeyValueRepositoryFactory repositoryFactory = new KeyValueRepositoryFactory(new KeyValueTemplate(new MapKeyValueAdapter()));
private MockRepository mockRepository = repositoryFactory.getRepository(MockRepository.class);
private final KeyValueRepositoryFactory repositoryFactory =
new KeyValueRepositoryFactory(new KeyValueTemplate(new MapKeyValueAdapter()));
private final MockRepository mockRepository = repositoryFactory.getRepository(MockRepository.class);
@Test
void shouldFetchSingleItems() {
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
mockRepository.save(book);
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, QuerydslPredicateExecutor<?>> tester =
(wiringConfigurer, executor) -> {
WebGraphQlHandler handler = initWebGraphQlHandler(wiringConfigurer, executor, null);
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
Consumer<WebGraphQlHandler> tester = (handler) -> {
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
// TODO: getData interferes with method overrides
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById",
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy")));
};
Map<String, Object> map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", book.getName());
};
// explicit wiring
tester.accept(
builder -> builder.dataFetcher("bookById", QuerydslDataFetcher.builder(mockRepository).single()),
null);
tester.accept(initHandler("bookById", QuerydslDataFetcher.builder(mockRepository).single()));
// auto registration
tester.accept(null, mockRepository);
tester.accept(initHandler(builder -> {}, mockRepository, null));
}
@Test
@@ -96,24 +96,20 @@ class QuerydslDataFetcherTests {
Book book2 = new Book(53L, "Breaking Bad", new Author(0L, "", "Heisenberg"));
mockRepository.saveAll(Arrays.asList(book1, book2));
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, QuerydslPredicateExecutor<?>> tester =
(wiringConfigurer, executor) -> {
WebGraphQlHandler handler = initWebGraphQlHandler(wiringConfigurer, mockRepository, null);
WebOutput output = handler.handleRequest(input("{ books {name}}")).block();
Consumer<WebGraphQlHandler> tester = (handler) -> {
WebOutput output = handler.handleRequest(input("{ books {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("books", Arrays.asList(
Collections.singletonMap("name", "Breaking Bad"),
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"))));
};
List<Map<String, Object>> data = GraphQlTestUtils.getData(output, "books");
assertThat(data).containsExactlyInAnyOrder(
Collections.singletonMap("name", "Breaking Bad"),
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"));
};
// explicit wiring
tester.accept(
builder -> builder.dataFetcher("books", QuerydslDataFetcher.builder(mockRepository).many()),
null);
tester.accept(initHandler("books", QuerydslDataFetcher.builder(mockRepository).many()));
// auto registration
tester.accept(null, mockRepository);
tester.accept(initHandler(builder -> {}, mockRepository, null));
}
@Test
@@ -123,21 +119,21 @@ class QuerydslDataFetcherTests {
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
// 1) Automatic registration only
WebGraphQlHandler handler = initWebGraphQlHandler(null, mockRepository, null);
WebGraphQlHandler handler = initHandler(builder -> {}, mockRepository, null);
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById", Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy")));
Map<String, Object> map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", "Hitchhiker's Guide to the Galaxy");
// 2) Automatic registration and explicit wiring
handler = initWebGraphQlHandler(
builder -> builder.dataFetcher("bookById", env -> new Book(53L, "Breaking Bad", new Author(0L, "", "Heisenberg"))),
mockRepository, null);
handler = initHandler(
"bookById", env -> new Book(53L, "Breaking Bad", new Author(0L, "", "Heisenberg")),
mockRepository);
output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById", Collections.singletonMap("name", "Breaking Bad")));
map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", "Breaking Bad");
}
@Test
@@ -145,17 +141,13 @@ class QuerydslDataFetcherTests {
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
mockRepository.save(book);
WebGraphQlHandler handler = initWebGraphQlHandler(builder -> builder
.dataFetcher("bookById", QuerydslDataFetcher
.builder(mockRepository)
.projectAs(BookProjection.class)
.single()));
WebGraphQlHandler handler = initHandler("bookById",
QuerydslDataFetcher.builder(mockRepository).projectAs(BookProjection.class).single());
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById",
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy by Douglas Adams")));
Map<String, Object> map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", "Hitchhiker's Guide to the Galaxy by Douglas Adams");
}
@Test
@@ -163,29 +155,24 @@ class QuerydslDataFetcherTests {
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
mockRepository.save(book);
WebGraphQlHandler handler = initWebGraphQlHandler(builder -> builder
.dataFetcher("bookById", QuerydslDataFetcher
.builder(mockRepository)
.projectAs(BookDto.class)
.single()));
WebGraphQlHandler handler = initHandler("bookById",
QuerydslDataFetcher.builder(mockRepository).projectAs(BookDto.class).single());
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById",
Collections.singletonMap("name", "The book is: Hitchhiker's Guide to the Galaxy")));
Map<String, Object> map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", "The book is: Hitchhiker's Guide to the Galaxy");
}
@Test
void shouldConstructPredicateProperly() {
MockRepository mockRepository = mock(MockRepository.class);
WebGraphQlHandler handler = initWebGraphQlHandler(builder -> builder
.dataFetcher("books", QuerydslDataFetcher
.builder(mockRepository)
WebGraphQlHandler handler = initHandler("books",
QuerydslDataFetcher.builder(mockRepository)
.customizer((QuerydslBinderCustomizer<QBook>) (bindings, book) ->
bindings.bind(book.name).firstOptional((path, value) -> value.map(path::startsWith)))
.many()));
.many());
handler.handleRequest(input("{ books(name: \"H\", author: \"Doug\") {name}}")).block();
@@ -202,24 +189,18 @@ class QuerydslDataFetcherTests {
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
when(mockRepository.findBy(any(), any())).thenReturn(Mono.just(book));
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, ReactiveQuerydslPredicateExecutor<?>> tester =
(wiringConfigurer, executor) -> {
WebGraphQlHandler handler = initWebGraphQlHandler(wiringConfigurer, null, executor);
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
Consumer<WebGraphQlHandler> tester = (handler) -> {
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
// TODO: getData interferes with method overrides
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("bookById",
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy")));
};
Map<String, Object> map = GraphQlTestUtils.getData(output, "bookById");
assertThat(map).hasSize(1).containsEntry("name", book.getName());
};
// explicit wiring
tester.accept(
builder -> builder.dataFetcher("bookById", QuerydslDataFetcher.builder(mockRepository).single()),
null);
tester.accept(initHandler("bookById", QuerydslDataFetcher.builder(mockRepository).single()));
// auto registration
tester.accept(null, mockRepository);
tester.accept(initHandler(builder -> {}, null, mockRepository));
}
@Test
@@ -229,24 +210,20 @@ class QuerydslDataFetcherTests {
Book book2 = new Book(53L, "Breaking Bad", new Author(0L, "", "Heisenberg"));
when(mockRepository.findBy(any(), any())).thenReturn(Flux.just(book1, book2));
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, ReactiveQuerydslPredicateExecutor<?>> tester =
(wiringConfigurer, executor) -> {
WebGraphQlHandler handler = initWebGraphQlHandler(wiringConfigurer, null, mockRepository);
WebOutput output = handler.handleRequest(input("{ books {name}}")).block();
Consumer<WebGraphQlHandler> tester = (handler) -> {
WebOutput output = handler.handleRequest(input("{ books {name}}")).block();
assertThat((Object) output.getData()).isEqualTo(
Collections.singletonMap("books", Arrays.asList(
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"),
Collections.singletonMap("name", "Breaking Bad"))));
};
List<Map<String, Object>> data = GraphQlTestUtils.getData(output, "books");
assertThat(data).containsExactlyInAnyOrder(
Collections.singletonMap("name", "Breaking Bad"),
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"));
};
// explicit wiring
tester.accept(
builder -> builder.dataFetcher("books", QuerydslDataFetcher.builder(mockRepository).many()),
null);
tester.accept(initHandler("books", QuerydslDataFetcher.builder(mockRepository).many()));
// auto registration
tester.accept(null, mockRepository);
tester.accept(initHandler(builder -> {}, null, mockRepository));
}
@@ -260,49 +237,48 @@ class QuerydslDataFetcherTests {
}
static WebGraphQlHandler initWebGraphQlHandler(Consumer<TypeRuntimeWiring.Builder> configurer) {
return initWebGraphQlHandler(configurer, null, null);
static WebGraphQlHandler initHandler(String fieldName, DataFetcher<?> fetcher) {
return initHandler(fieldName, fetcher, null);
}
static WebGraphQlHandler initWebGraphQlHandler(
@Nullable Consumer<TypeRuntimeWiring.Builder> configurer,
static WebGraphQlHandler initHandler(
String fieldName, DataFetcher<?> fetcher, @Nullable QuerydslPredicateExecutor<?> executor) {
return initHandler(
GraphQlTestUtils.graphQlSource(BookSource.schema, "Query", fieldName, fetcher),
executor, null);
}
static WebGraphQlHandler initHandler(
RuntimeWiringConfigurer configurer,
@Nullable QuerydslPredicateExecutor<?> executor,
@Nullable ReactiveQuerydslPredicateExecutor<?> reactiveExecutor) {
return WebGraphQlHandler
.builder(new ExecutionGraphQlService(graphQlSource(configurer, executor, reactiveExecutor)))
.build();
return initHandler(
GraphQlTestUtils.graphQlSource(BookSource.schema, configurer),
executor,
reactiveExecutor);
}
private static GraphQlSource graphQlSource(
@Nullable Consumer<TypeRuntimeWiring.Builder> configurer,
private static WebGraphQlHandler initHandler(
GraphQlSource.Builder sourceBuilder,
@Nullable QuerydslPredicateExecutor<?> executor,
@Nullable ReactiveQuerydslPredicateExecutor<?> reactiveExecutor) {
GraphQlSource.Builder graphQlSourceBuilder = GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"));
if (configurer != null) {
TypeRuntimeWiring.Builder typeBuilder = TypeRuntimeWiring.newTypeWiring("Query");
configurer.accept(typeBuilder);
graphQlSourceBuilder.configureRuntimeWiring(wiring -> wiring.type(typeBuilder));
}
GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor(
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
(reactiveExecutor != null ? Collections.singletonList(reactiveExecutor): Collections.emptyList()));
(reactiveExecutor != null ? Collections.singletonList(reactiveExecutor) : Collections.emptyList()));
graphQlSourceBuilder.typeVisitors(Collections.singletonList(visitor));
return graphQlSourceBuilder.build();
GraphQlSource source = sourceBuilder.typeVisitors(Collections.singletonList(visitor)).build();
ExecutionGraphQlService service = new ExecutionGraphQlService(source);
return WebGraphQlHandler.builder(service).build();
}
private WebInput input(String query) {
return new WebInput(
URI.create("http://abc.org"), new HttpHeaders(), Collections.singletonMap("query", query),
null, "1");
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
}
interface BookProjection {
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")

View File

@@ -24,10 +24,10 @@ import org.dataloader.DataLoader;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import org.springframework.core.io.ClassPathResource;
import org.springframework.graphql.Author;
import org.springframework.graphql.Book;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.RequestInput;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,8 +40,23 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class BatchLoadingTests {
private final BatchLoaderRegistry registry = new DefaultBatchLoaderRegistry();
@Test
void batchLoader() {
String query = "{ " +
" booksByCriteria(criteria: {author:\"Orwell\"}) { " +
" author {" +
" firstName, " +
" lastName " +
" }" +
" }" +
"}";
this.registry.forTypePair(Long.class, Author.class)
.registerBatchLoader((ids, env) -> Flux.fromIterable(ids).map(BookSource::getAuthor));
ExecutionGraphQlService service = initExecutionGraphQlService(wiring -> {
wiring.type("Query", builder -> builder.dataFetcher("booksByCriteria", env -> {
Map<String, Object> criteria = env.getArgument("criteria");
@@ -57,23 +72,7 @@ public class BatchLoadingTests {
}));
});
BatchLoaderRegistry registry = new DefaultBatchLoaderRegistry();
registry.forTypePair(Long.class, Author.class)
.registerBatchLoader((ids, env) -> Flux.fromIterable(ids).map(BookSource::getAuthor));
service.addDataLoaderRegistrar(registry);
String query = "{ " +
" booksByCriteria(criteria: {author:\"Orwell\"}) { " +
" author {" +
" firstName, " +
" lastName " +
" }" +
" }" +
"}";
RequestInput input = new RequestInput(query, null, null, null);
ExecutionResult result = service.execute(input).block();
ExecutionResult result = service.execute(new RequestInput(query, null, null, null)).block();
assertThat(result.getErrors()).isEmpty();
Map<String, Object> data = result.getData();
@@ -88,11 +87,10 @@ public class BatchLoadingTests {
}
private ExecutionGraphQlService initExecutionGraphQlService(RuntimeWiringConfigurer configurer) {
GraphQlSource graphQlSource = GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.configureRuntimeWiring(configurer)
.build();
return new ExecutionGraphQlService(graphQlSource);
GraphQlSource source = GraphQlTestUtils.graphQlSource(BookSource.schema, configurer).build();
ExecutionGraphQlService service = new ExecutionGraphQlService(source);
service.addDataLoaderRegistrar(this.registry);
return service;
}
@SuppressWarnings("unchecked")

View File

@@ -67,7 +67,7 @@ public class ClassNameTypeResolverTests {
void typeResolutionViaSuperHierarchy() {
GraphQlSource graphQlSource =
GraphQlTestUtils.initGraphQlSource(schema, "Query", "animals", env -> animalList).build();
GraphQlTestUtils.graphQlSource(schema, "Query", "animals", env -> animalList).build();
String query = "" +
"query Animals {" +
@@ -87,7 +87,7 @@ public class ClassNameTypeResolverTests {
.execute(new RequestInput(query, null, null, null))
.block();
List<Map<String, Object>> actualAnimals = GraphQlTestUtils.checkErrorsAndGetData(result, "animals");
List<Map<String, Object>> actualAnimals = GraphQlTestUtils.getData(result, "animals");
for (int i = 0; i < animalList.size(); i++) {
Map<String, Object> actualAnimal = actualAnimals.get(i);
@@ -113,7 +113,7 @@ public class ClassNameTypeResolverTests {
typeResolver.addMapping(Tree.class, "Plant");
GraphQlSource graphQlSource =
GraphQlTestUtils.initGraphQlSource(schema, "Query", "sightings", env -> animalAndPlantList)
GraphQlTestUtils.graphQlSource(schema, "Query", "sightings", env -> animalAndPlantList)
.defaultTypeResolver(typeResolver)
.build();
@@ -137,7 +137,7 @@ public class ClassNameTypeResolverTests {
.execute(new RequestInput(query, null, null, null))
.block();
List<Map<String, Object>> actualSightings = GraphQlTestUtils.checkErrorsAndGetData(result, "sightings");
List<Map<String, Object>> actualSightings = GraphQlTestUtils.getData(result, "sightings");
for (int i = 0; i < animalAndPlantList.size(); i++) {
Map<String, Object> actualSighting = actualSightings.get(i);

View File

@@ -23,6 +23,7 @@ import java.util.Map;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -37,13 +38,15 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ContextDataFetcherDecorator}.
* @author Rossen Stoyanchev
*/
public class ContextDataFetcherDecoratorTests {
@Test
void monoDataFetcher() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> Mono.deferContextual((context) -> {
GraphQL graphQl = initGraphQl(
"type Query { greeting: String }",
"Query", "greeting", (env) -> Mono.deferContextual((context) -> {
Object name = context.get("name");
return Mono.delay(Duration.ofMillis(50)).map((aLong) -> "Hello " + name);
}));
@@ -58,25 +61,29 @@ public class ContextDataFetcherDecoratorTests {
@Test
void fluxDataFetcher() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greetings: [String] }", "Query", "greetings",
(env) -> Mono.delay(Duration.ofMillis(50)).flatMapMany((aLong) -> Flux.deferContextual((context) -> {
String name = context.get("name");
return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
})));
GraphQL graphQl = initGraphQl(
"type Query { greetings: [String] }",
"Query", "greetings",
(env) -> Mono.delay(Duration.ofMillis(50))
.flatMapMany((aLong) -> Flux.deferContextual((context) -> {
String name = context.get("name");
return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
})));
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greetings }").build();
ReactorContextManager.setReactorContext(Context.of("name", "007"), input);
Map<String, Object> data = graphQl.executeAsync(input).get().getData();
ExecutionResult result = graphQl.executeAsync(input).get();
assertThat((List<String>) data.get("greetings")).containsExactly("Hi 007", "Bonjour 007", "Hola 007");
List<String> data = GraphQlTestUtils.getData(result, "greetings");
assertThat(data).containsExactly("Hi 007", "Bonjour 007", "Hola 007");
}
@Test
void fluxDataFetcherSubscription() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(
"type Query { greeting: String } type Subscription { greetings: String }", "Subscription", "greetings",
(env) -> Mono.delay(Duration.ofMillis(50))
GraphQL graphQl = initGraphQl(
"type Query { greeting: String } type Subscription { greetings: String }",
"Subscription", "greetings", (env) -> Mono.delay(Duration.ofMillis(50))
.flatMapMany((aLong) -> Flux.deferContextual((context) -> {
String name = context.get("name");
return Flux.just("Hi", "Bonjour", "Hola").map((s) -> s + " " + name);
@@ -88,8 +95,7 @@ public class ContextDataFetcherDecoratorTests {
Publisher<String> publisher = graphQl.executeAsync(input).get().getData();
List<String> actual = Flux.from(publisher).cast(ExecutionResult.class)
.map((result) -> ((Map<String, ?>) result.getData()).get("greetings"))
.cast(String.class)
.map((result) -> GraphQlTestUtils.<String>getData(result, "greetings"))
.collectList()
.block();
@@ -102,9 +108,9 @@ public class ContextDataFetcherDecoratorTests {
nameThreadLocal.set("007");
TestThreadLocalAccessor<String> accessor = new TestThreadLocalAccessor<>(nameThreadLocal);
try {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(
"type Query { greeting: String }", "Query", "greeting",
(env) -> "Hello " + nameThreadLocal.get());
GraphQL graphQl = initGraphQl(
"type Query { greeting: String }",
"Query", "greeting", (env) -> "Hello " + nameThreadLocal.get());
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ContextView view = ReactorContextManager.extractThreadLocalValues(accessor, Context.empty());
@@ -114,7 +120,7 @@ public class ContextDataFetcherDecoratorTests {
.flatMap((aLong) -> Mono.fromFuture(graphQl.executeAsync(input)))
.block();
Map<String, Object> data = result.getData();
Map<String, Object> data = GraphQlTestUtils.getData(result);
assertThat(data).hasSize(1).containsEntry("greeting", "Hello 007");
}
finally {
@@ -122,4 +128,11 @@ public class ContextDataFetcherDecoratorTests {
}
}
private static GraphQL initGraphQl(
String schemaContent, String typeName, String fieldName, DataFetcher<?> fetcher) {
return GraphQlTestUtils.graphQlSource(schemaContent, typeName, fieldName, fetcher)
.build().graphQl();
}
}

View File

@@ -38,6 +38,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
/**
* Unit tests for {@link DefaultBatchLoaderRegistry}.
* @author Rossen Stoyanchev
*/
public class DefaultBatchLoaderRegistryTests {

View File

@@ -40,16 +40,14 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ExceptionResolversExceptionHandler}.
* @author Rossen Stoyanchev
*/
public class ExceptionResolversExceptionHandlerTests {
@Test
void resolveException() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
(ex, env) -> Mono.just(Collections.singletonList(
GraphQL graphQl = initGraphQl((ex, env) ->
Mono.just(Collections.singletonList(
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage())
.errorType(ErrorType.BAD_REQUEST).build())));
@@ -68,11 +66,8 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void resolveExceptionWithReactorContext() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
(ex, env) -> Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
GraphQL graphQl = initGraphQl((ex, env) ->
Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + view.get("name"))
.errorType(ErrorType.BAD_REQUEST).build()))));
@@ -92,15 +87,11 @@ public class ExceptionResolversExceptionHandlerTests {
nameThreadLocal.set("007");
TestThreadLocalAccessor<String> accessor = new TestThreadLocalAccessor<>(nameThreadLocal);
try {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
threadLocalContextAwareExceptionResolver((ex, env) ->
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
.errorType(ErrorType.BAD_REQUEST)
.build()));
GraphQL graphQl = initGraphQl(threadLocalContextAwareExceptionResolver((ex, env) ->
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
.errorType(ErrorType.BAD_REQUEST)
.build()));
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ContextView view = ReactorContextManager.extractThreadLocalValues(accessor, Context.empty());
@@ -120,11 +111,7 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void unresolvedException() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
(exception, environment) -> Mono.empty());
GraphQL graphQl = initGraphQl((exception, environment) -> Mono.empty());
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ExecutionResult result = graphQl.executeAsync(input).get();
@@ -141,11 +128,7 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void suppressedException() throws Exception {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
(ex, env) -> Mono.just(Collections.emptyList()));
GraphQL graphQl = initGraphQl((ex, env) -> Mono.just(Collections.emptyList()));
ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ExecutionResult result = graphQl.executeAsync(input).get();
@@ -155,6 +138,17 @@ public class ExceptionResolversExceptionHandlerTests {
assertThat(result.getErrors()).hasSize(0);
}
private static GraphQL initGraphQl(DataFetcherExceptionResolver exceptionResolver) {
return GraphQlTestUtils.graphQlSource(
"type Query { greeting: String }",
"Query", "greeting", (env) -> {
throw new IllegalArgumentException("Invalid greeting");
})
.exceptionResolvers(Collections.singletonList(exceptionResolver))
.build()
.graphQl();
}
private static DataFetcherExceptionResolver threadLocalContextAwareExceptionResolver(
BiFunction<Throwable, DataFetchingEnvironment, GraphQLError> resolver) {

View File

@@ -18,11 +18,10 @@ package org.springframework.graphql.web;
import java.util.Arrays;
import graphql.schema.idl.TypeRuntimeWiring;
import reactor.core.publisher.Flux;
import org.springframework.core.io.ClassPathResource;
import org.springframework.graphql.BookSource;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
@@ -61,26 +60,23 @@ public abstract class BookTestUtils {
"}";
public static WebGraphQlHandler initWebGraphQlHandler(WebInterceptor... interceptors) {
return WebGraphQlHandler.builder(new ExecutionGraphQlService(graphQlSource()))
GraphQlSource source = GraphQlTestUtils.graphQlSource(BookSource.schema,
wiringBuilder -> {
wiringBuilder.type("Query", builder -> builder.dataFetcher("bookById", (env) -> {
Long id = Long.parseLong(env.getArgument("id"));
return BookSource.getBook(id);
}));
wiringBuilder.type("Subscription", builder -> builder.dataFetcher("bookSearch", (env) -> {
String author = env.getArgument("author");
return Flux.fromIterable(BookSource.books())
.filter((book) -> book.getAuthor().getFullName().contains(author));
}));
}).build();
return WebGraphQlHandler.builder(new ExecutionGraphQlService(source))
.interceptors(Arrays.asList(interceptors))
.build();
}
private static GraphQlSource graphQlSource() {
return GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.configureRuntimeWiring(builder -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", (env) -> {
Long id = Long.parseLong(env.getArgument("id"));
return BookSource.getBook(id);
}))
.type(TypeRuntimeWiring.newTypeWiring("Subscription")
.dataFetcher("bookSearch", (env) -> {
String author = env.getArgument("author");
return Flux.fromIterable(BookSource.books())
.filter((book) -> book.getAuthor().getFullName().contains(author));
})))
.build();
}
}

View File

@@ -23,7 +23,6 @@ import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
@@ -32,12 +31,12 @@ import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.TestGraphQlSource;
import org.springframework.graphql.TestThreadLocalAccessor;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.http.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,17 +52,18 @@ public class WebGraphQlHandlerTests {
@Test
void reactorContextPropagation() {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(
"type Query { greeting: String }", "Query", "greeting",
(env) -> Mono.deferContextual((context) -> {
GraphQlSource graphQlSource = GraphQlTestUtils.graphQlSource(
"type Query { greeting: String }",
"Query", "greeting", (env) -> Mono.deferContextual((context) -> {
Object name = context.get("name");
return Mono.delay(Duration.ofMillis(50)).map((aLong) -> "Hello " + name);
}));
})).build();
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlService service = new ExecutionGraphQlService(graphQlSource);
WebGraphQlHandler handler = WebGraphQlHandler.builder(service).build();
WebOutput webOutput = handler.handleRequest(webInput).contextWrite((context) -> context.put("name", "007")).block();
WebOutput webOutput = handler.handleRequest(webInput)
.contextWrite((context) -> context.put("name", "007")).block();
Map<String, Object> data = webOutput.getData();
assertThat(data).hasSize(1).containsEntry("greeting", "Hello 007");
@@ -71,19 +71,25 @@ public class WebGraphQlHandlerTests {
@Test
void reactorContextPropagationToExceptionResolver() {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
throw new IllegalArgumentException("Invalid greeting");
},
(ex, env) -> Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
GraphqlErrorBuilder
.newError(env).message("Resolved error: " + ex.getMessage() + ", name=" + view.get("name"))
.errorType(ErrorType.BAD_REQUEST).build()))));
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlSource graphQlSource = GraphQlTestUtils.graphQlSource(
"type Query { greeting: String }",
"Query", "greeting", (env) -> {
throw new IllegalArgumentException("Invalid greeting");
})
.exceptionResolvers(Collections.singletonList(
(ex, env) -> Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + view.get("name"))
.errorType(ErrorType.BAD_REQUEST)
.build())))))
.build();
GraphQlService service = new ExecutionGraphQlService(graphQlSource);
WebGraphQlHandler handler = WebGraphQlHandler.builder(service).build();
WebOutput webOutput = handler.handleRequest(webInput).contextWrite((context) -> context.put("name", "007")).block();
WebOutput webOutput = handler.handleRequest(webInput)
.contextWrite((context) -> context.put("name", "007")).block();
Map<String, Object> data = webOutput.getData();
assertThat(data).hasSize(1).containsEntry("greeting", null);
@@ -99,11 +105,12 @@ public class WebGraphQlHandlerTests {
nameThreadLocal.set("007");
TestThreadLocalAccessor<String> threadLocalAccessor = new TestThreadLocalAccessor<>(nameThreadLocal);
try {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(
"type Query { greeting: String }", "Query", "greeting",
(env) -> "Hello " + nameThreadLocal.get());
GraphQlSource graphQlSource = GraphQlTestUtils.graphQlSource(
"type Query { greeting: String }",
"Query", "greeting", env -> "Hello " + nameThreadLocal.get())
.build();
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlService service = new ExecutionGraphQlService(graphQlSource);
WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
.interceptor((input, next) -> Mono.delay(Duration.ofMillis(10)).flatMap((aLong) -> next.next(input)))
@@ -125,16 +132,20 @@ public class WebGraphQlHandlerTests {
nameThreadLocal.set("007");
TestThreadLocalAccessor<String> threadLocalAccessor = new TestThreadLocalAccessor<>(nameThreadLocal);
try {
GraphQL graphQl = GraphQlTestUtils.initGraphQl("type Query { greeting: String }", "Query", "greeting",
(env) -> {
GraphQlSource graphQlSource = GraphQlTestUtils.graphQlSource(
"type Query { greeting: String }",
"Query", "greeting", env -> {
throw new IllegalArgumentException("Invalid greeting");
},
threadLocalContextAwareExceptionResolver((ex, env) ->
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
.errorType(ErrorType.BAD_REQUEST).build()));
})
.exceptionResolvers(Collections.singletonList(
threadLocalContextAwareExceptionResolver((ex, env) ->
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
.errorType(ErrorType.BAD_REQUEST).build())
))
.build();
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlService service = new ExecutionGraphQlService(graphQlSource);
WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
.interceptor((input, next) -> Mono.delay(Duration.ofMillis(10)).flatMap((aLong) -> next.next(input)))

View File

@@ -20,15 +20,14 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import graphql.GraphQL;
import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.TestGraphQlSource;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
@@ -68,8 +67,8 @@ public class GraphQlHttpHandlerTests {
private GraphQlHttpHandler createHttpHandler(
String schemaContent, String type, String field, DataFetcher<Object> dataFetcher) {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(schemaContent, type, field, dataFetcher);
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlSource source = GraphQlTestUtils.graphQlSource(schemaContent, type, field, dataFetcher).build();
GraphQlService service = new ExecutionGraphQlService(source);
return new GraphQlHttpHandler(WebGraphQlHandler.builder(service).build());
}

View File

@@ -23,15 +23,14 @@ import java.util.Locale;
import javax.servlet.ServletException;
import graphql.GraphQL;
import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.GraphQlTestUtils;
import org.springframework.graphql.TestGraphQlSource;
import org.springframework.graphql.execution.ExecutionGraphQlService;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@@ -78,8 +77,8 @@ public class GraphQlHttpHandlerTests {
private GraphQlHttpHandler createHttpHandler(
String schemaContent, String type, String field, DataFetcher<Object> dataFetcher) {
GraphQL graphQl = GraphQlTestUtils.initGraphQl(schemaContent, type, field, dataFetcher);
GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl));
GraphQlSource source = GraphQlTestUtils.graphQlSource(schemaContent, type, field, dataFetcher).build();
GraphQlService service = new ExecutionGraphQlService(source);
return new GraphQlHttpHandler(WebGraphQlHandler.builder(service).build());
}