From e053e1415856a35ea05ec5f546136e8b0f8efdd1 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 22 Oct 2021 21:50:05 +0200 Subject: [PATCH] Add GraphQlTest annotation Prior to this commit, we could only test Spring GraphQL applications with a complete application - all application and infrastructure components were involved. While using `@SpringBootTest` is often useful for complete integration tests (with or without a live running server), we often want to write lean integration tests and test slices of our application. Just like `@WebMvcTest` or `@WebFluxTest`, this commit introduces the support for `@GraphQlTest`. This annotation helps us to test a particular slice of our application: a hand-picked selection of `@Controller`, plus `RuntimeWiringConfigurer` and `WebInterceptor` beans. Other `@Component` must be imported or mocked for those tests. This commit also refactors the existing auto-configuration to enable this use case. The `WebGraphQlHandlerAutoConfiguration` now holds the required components for `@GraphQlTest`, while other web-related auto-configurations bring the web framework and transport infrastructures. Closes gh-75 --- graphql-spring-boot-starter/build.gradle | 1 + .../boot/GraphQlWebFluxAutoConfiguration.java | 24 +-- .../boot/GraphQlWebMvcAutoConfiguration.java | 27 +-- ...> WebGraphQlHandlerAutoConfiguration.java} | 30 ++- .../boot/test/AutoConfigureGraphQl.java | 44 +++++ .../graphql/boot/test/GraphQlTest.java | 143 ++++++++++++++ .../test/GraphQlTestContextBootstrapper.java | 35 ++++ .../boot/test/GraphQlTypeExcludeFilter.java | 94 +++++++++ ...ava => AutoConfigureWebGraphQlTester.java} | 11 +- ...n.java => GraphQlTesterConfiguration.java} | 28 ++- .../WebGraphQlTesterAutoConfiguration.java | 39 ++++ .../main/resources/META-INF/spring.factories | 15 +- .../GraphQlWebFluxAutoConfigurationTests.java | 2 +- .../GraphQlWebMvcAutoConfigurationTests.java | 2 +- ...bGraphQlHandlerAutoConfigurationTests.java | 64 ++++++ ...WebFluxSecurityAutoConfigurationTests.java | 4 +- ...lWebMvcSecurityAutoConfigurationTests.java | 4 +- .../graphql/boot/test/BookController.java | 38 ++++ .../boot/test/ExampleGraphQlApplication.java | 30 +++ .../boot/test/GraphQlTestIntegrationTest.java | 50 +++++ .../test/GraphQlTypeExcludeFilterTests.java | 184 ++++++++++++++++++ .../GraphQlTesterAutoConfigurationTests.java | 12 +- .../graphql/SampleApplicationTests.java | 4 +- .../graphql/SampleApplicationTests.java | 4 +- .../graphql/project/MockMvcGraphQlTests.java | 4 +- .../src/docs/asciidoc/boot-starter.adoc | 138 +++++++++---- .../graphql/test/tester/WebGraphQlTester.java | 4 +- 27 files changed, 901 insertions(+), 134 deletions(-) rename graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/{GraphQlServiceAutoConfiguration.java => WebGraphQlHandlerAutoConfiguration.java} (61%) create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/AutoConfigureGraphQl.java create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTest.java create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTestContextBootstrapper.java create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilter.java rename graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/{AutoConfigureGraphQlTester.java => AutoConfigureWebGraphQlTester.java} (81%) rename graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/{GraphQlTesterAutoConfiguration.java => GraphQlTesterConfiguration.java} (64%) create mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebGraphQlTesterAutoConfiguration.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfigurationTests.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/BookController.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/ExampleGraphQlApplication.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTestIntegrationTest.java create mode 100644 graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilterTests.java diff --git a/graphql-spring-boot-starter/build.gradle b/graphql-spring-boot-starter/build.gradle index 336631d1..1b4cf617 100644 --- a/graphql-spring-boot-starter/build.gradle +++ b/graphql-spring-boot-starter/build.gradle @@ -48,6 +48,7 @@ dependencies { compileOnly project(':spring-graphql-test') compileOnly 'org.junit.jupiter:junit-jupiter-api' compileOnly 'org.springframework.boot:spring-boot-test' + compileOnly 'org.springframework.boot:spring-boot-test-autoconfigure' compileOnly 'com.google.code.findbugs:jsr305' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java index 2600133c..79c8368d 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java @@ -17,14 +17,11 @@ package org.springframework.graphql.boot; import java.util.Collections; -import java.util.stream.Collectors; import graphql.GraphQL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -37,12 +34,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.format.support.FormattingConversionService; -import org.springframework.graphql.GraphQlService; -import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.execution.GraphQlSource; import org.springframework.graphql.web.WebGraphQlHandler; -import org.springframework.graphql.web.WebInterceptor; import org.springframework.graphql.web.webflux.GraphQlHttpHandler; import org.springframework.graphql.web.webflux.GraphQlWebSocketHandler; import org.springframework.graphql.web.webflux.GraphiQlHandler; @@ -75,27 +68,12 @@ import static org.springframework.web.reactive.function.server.RequestPredicates @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnClass({GraphQL.class, GraphQlHttpHandler.class}) @ConditionalOnBean(GraphQlSource.class) -@AutoConfigureAfter(GraphQlServiceAutoConfiguration.class) +@AutoConfigureAfter(WebGraphQlHandlerAutoConfiguration.class) @EnableConfigurationProperties(GraphQlCorsProperties.class) public class GraphQlWebFluxAutoConfiguration { private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class); - @Bean - public AnnotatedControllerConfigurer annotatedControllerConfigurer(@Qualifier("webFluxConversionService") FormattingConversionService conversionService) { - AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer(); - annotatedControllerConfigurer.setConversionService(conversionService); - return annotatedControllerConfigurer; - } - - @Bean - @ConditionalOnBean(GraphQlService.class) - @ConditionalOnMissingBean - public WebGraphQlHandler webGraphQlHandler(GraphQlService service, ObjectProvider interceptors) { - return WebGraphQlHandler.builder(service) - .interceptors(interceptors.orderedStream().collect(Collectors.toList())).build(); - } - @Bean @ConditionalOnMissingBean public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java index 91d8675f..7ea690ed 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.graphql.boot; import java.util.Collections; import java.util.Map; -import java.util.stream.Collectors; import javax.websocket.server.ServerContainer; @@ -26,8 +25,6 @@ import graphql.GraphQL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -41,13 +38,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.format.support.FormattingConversionService; -import org.springframework.graphql.GraphQlService; -import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.execution.GraphQlSource; -import org.springframework.graphql.execution.ThreadLocalAccessor; import org.springframework.graphql.web.WebGraphQlHandler; -import org.springframework.graphql.web.WebInterceptor; import org.springframework.graphql.web.webmvc.GraphQlHttpHandler; import org.springframework.graphql.web.webmvc.GraphQlWebSocketHandler; import org.springframework.graphql.web.webmvc.GraphiQlHandler; @@ -82,29 +74,12 @@ import static org.springframework.web.servlet.function.RequestPredicates.content @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass({GraphQL.class, GraphQlHttpHandler.class}) @ConditionalOnBean(GraphQlSource.class) -@AutoConfigureAfter(GraphQlServiceAutoConfiguration.class) +@AutoConfigureAfter(WebGraphQlHandlerAutoConfiguration.class) @EnableConfigurationProperties(GraphQlCorsProperties.class) public class GraphQlWebMvcAutoConfiguration { private static final Log logger = LogFactory.getLog(GraphQlWebMvcAutoConfiguration.class); - @Bean - public AnnotatedControllerConfigurer annotatedControllerConfigurer(@Qualifier("mvcConversionService") FormattingConversionService conversionService) { - AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer(); - annotatedControllerConfigurer.setConversionService(conversionService); - return annotatedControllerConfigurer; - } - - @Bean - @ConditionalOnBean(GraphQlService.class) - @ConditionalOnMissingBean - public WebGraphQlHandler webGraphQlHandler(GraphQlService service, ObjectProvider interceptorsProvider, - ObjectProvider accessorsProvider) { - return WebGraphQlHandler.builder(service) - .interceptors(interceptorsProvider.orderedStream().collect(Collectors.toList())) - .threadLocalAccessors(accessorsProvider.orderedStream().collect(Collectors.toList())).build(); - } - @Bean @ConditionalOnMissingBean public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) { diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlServiceAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfiguration.java similarity index 61% rename from graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlServiceAutoConfiguration.java rename to graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfiguration.java index 8365cdc5..14dd1b11 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlServiceAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfiguration.java @@ -16,23 +16,31 @@ package org.springframework.graphql.boot; +import java.util.stream.Collectors; + import graphql.GraphQL; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; import org.springframework.graphql.execution.BatchLoaderRegistry; import org.springframework.graphql.execution.DefaultBatchLoaderRegistry; import org.springframework.graphql.execution.ExecutionGraphQlService; import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.execution.ThreadLocalAccessor; +import org.springframework.graphql.web.WebGraphQlHandler; +import org.springframework.graphql.web.WebInterceptor; /** * {@link EnableAutoConfiguration Auto-configuration} for creating a - * {@link GraphQlService}. + * {@link WebGraphQlHandler}. * * @author Brian Clozel * @since 1.0.0 @@ -41,11 +49,12 @@ import org.springframework.graphql.execution.GraphQlSource; @ConditionalOnClass({GraphQL.class, GraphQlService.class}) @ConditionalOnMissingBean(GraphQlService.class) @AutoConfigureAfter(GraphQlAutoConfiguration.class) -public class GraphQlServiceAutoConfiguration { +public class WebGraphQlHandlerAutoConfiguration { private final BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry(); @Bean + @ConditionalOnMissingBean public BatchLoaderRegistry batchLoaderRegistry() { return this.batchLoaderRegistry; } @@ -58,4 +67,21 @@ public class GraphQlServiceAutoConfiguration { return service; } + @Bean + @ConditionalOnMissingBean + public AnnotatedControllerConfigurer annotatedControllerConfigurer() { + AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer(); + annotatedControllerConfigurer.setConversionService(new DefaultFormattingConversionService()); + return annotatedControllerConfigurer; + } + + @Bean + @ConditionalOnMissingBean + public WebGraphQlHandler webGraphQlHandler(GraphQlService service, ObjectProvider interceptorsProvider, + ObjectProvider accessorsProvider) { + return WebGraphQlHandler.builder(service) + .interceptors(interceptorsProvider.orderedStream().collect(Collectors.toList())) + .threadLocalAccessors(accessorsProvider.orderedStream().collect(Collectors.toList())).build(); + } + } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/AutoConfigureGraphQl.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/AutoConfigureGraphQl.java new file mode 100644 index 00000000..42d976bb --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/AutoConfigureGraphQl.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020-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.boot.test; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; + +/** + * {@link ImportAutoConfiguration Auto-configuration imports} for typical Spring GraphQL + * tests. Most tests should consider using {@link GraphQlTest @GraphQlTest} rather than + * using this annotation directly. + * + * @author Brian Clozel + * @since 1.0.0 + * @see GraphQlTest + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ImportAutoConfiguration +public @interface AutoConfigureGraphQl { + +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTest.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTest.java new file mode 100644 index 00000000..fa248026 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTest.java @@ -0,0 +1,143 @@ +/* + * Copyright 2020-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.boot.test; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.core.AutoConfigureCache; +import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.env.Environment; +import org.springframework.graphql.boot.test.tester.AutoConfigureWebGraphQlTester; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Annotation that can be used for a Spring GraphQL test that focuses + * only on Spring GraphQL components, without involving web frameworks. + *

+ * Using this annotation will disable full auto-configuration and instead apply only + * configuration relevant to GraphQL tests (i.e. {@code @Controller}, + * {@code @JsonComponent}, {@code Converter}/{@code GenericConverter}, + * {@code WebInterceptor} and {@code RuntimeWiringConfigurer} beans but not + * {@code @Component}, {@code @Service} or {@code @Repository} beans). + *

+ * By default, tests annotated with {@code @GraphQlTest} will also auto-configure a + * {@link org.springframework.graphql.test.tester.WebGraphQlTester}. For more fine-grained control of GraphQlTester the + * {@link AutoConfigureWebGraphQlTester @AutoConfigureGraphQlTester} annotation can be used. + *

+ * Typically {@code @GraphQlTest} is used in combination with + * {@link org.springframework.boot.test.mock.mockito.MockBean @MockBean} or + * {@link org.springframework.context.annotation.Import @Import} to create any collaborators required by your + * {@code @Controller} beans. + *

+ * If you are looking to load your full application configuration and use {@code WebGraphQlTester}, + * you should consider {@link org.springframework.boot.test.context.SpringBootTest @SpringBootTest} combined with + * {@link AutoConfigureWebGraphQlTester @AutoConfigureWebGraphQlTester} rather than this + * annotation. + * + * @author Brian Clozel + * @since 1.0.0 + * @see AutoConfigureWebGraphQlTester + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@BootstrapWith(GraphQlTestContextBootstrapper.class) +@ExtendWith(SpringExtension.class) +@OverrideAutoConfiguration(enabled = false) +@TypeExcludeFilters(GraphQlTypeExcludeFilter.class) +@AutoConfigureCache +@AutoConfigureJson +@AutoConfigureGraphQl +@AutoConfigureWebGraphQlTester +@ImportAutoConfiguration +public @interface GraphQlTest { + + /** + * Properties in form {@literal key=value} that should be added to the Spring + * {@link Environment} before the test runs. + * @return the properties to add + */ + String[] properties() default {}; + + /** + * Specifies the controllers to test. This is an alias of {@link #controllers()} which + * can be used for brevity if no other attributes are defined. See + * {@link #controllers()} for details. + * @see #controllers() + * @return the controllers to test + */ + @AliasFor("controllers") + Class[] value() default {}; + + /** + * Specifies the controllers to test. May be left blank if all {@code @Controller} + * beans should be added to the application context. + * @see #value() + * @return the controllers to test + */ + @AliasFor("value") + Class[] controllers() default {}; + + /** + * Determines if default filtering should be used with + * {@link SpringBootApplication @SpringBootApplication}. By default, only + * {@code @Controller} (when no explicit {@link #controllers() controllers} are + * defined), {@code @ControllerAdvice}, {@code WebInterceptor} + * and {@code RuntimeWiringConfigurer} beans are + * included. + * @see #includeFilters() + * @see #excludeFilters() + * @return if default filters should be used + */ + boolean useDefaultFilters() default true; + + /** + * A set of include filters which can be used to add otherwise filtered beans to the + * application context. + * @return include filters to apply + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * A set of exclude filters which can be used to filter beans that would otherwise be + * added to the application context. + * @return exclude filters to apply + */ + ComponentScan.Filter[] excludeFilters() default {}; + + /** + * Auto-configuration exclusions that should be applied for this test. + * @return auto-configuration exclusions to apply + */ + @AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude") + Class[] excludeAutoConfiguration() default {}; +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTestContextBootstrapper.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTestContextBootstrapper.java new file mode 100644 index 00000000..023b719b --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTestContextBootstrapper.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020-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.boot.test; + +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.test.context.TestContextBootstrapper; + +/** + * {@link TestContextBootstrapper} for {@link GraphQlTest @GraphQlTest} + * + * @author Brian Clozel + */ +class GraphQlTestContextBootstrapper extends SpringBootTestContextBootstrapper { + + @Override + protected String[] getProperties(Class testClass) { + return MergedAnnotations.from(testClass, MergedAnnotations.SearchStrategy.INHERITED_ANNOTATIONS).get(GraphQlTest.class) + .getValue("properties", String[].class).orElse(null); + } +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilter.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilter.java new file mode 100644 index 00000000..7413feca --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020-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.boot.test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.boot.test.autoconfigure.filter.StandardAnnotationCustomizableTypeExcludeFilter; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; +import org.springframework.graphql.web.WebInterceptor; +import org.springframework.stereotype.Controller; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * {@link TypeExcludeFilter} for {@link GraphQlTest @GraphQlTest}. + * + * @author Brian Clozel + * @since 1.0.0 + */ +public class GraphQlTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter { + + private static final Class[] NO_CONTROLLERS = {}; + + private static final String[] OPTIONAL_INCLUDES = { "com.fasterxml.jackson.databind.Module"}; + + private static final Set> DEFAULT_INCLUDES; + + static { + Set> includes = new LinkedHashSet<>(); + includes.add(JsonComponent.class); + includes.add(RuntimeWiringConfigurer.class); + includes.add(Converter.class); + includes.add(GenericConverter.class); + includes.add(WebInterceptor.class); + for (String optionalInclude : OPTIONAL_INCLUDES) { + try { + includes.add(ClassUtils.forName(optionalInclude, null)); + } + catch (Exception ex) { + // Ignore + } + } + DEFAULT_INCLUDES = Collections.unmodifiableSet(includes); + } + + private static final Set> DEFAULT_INCLUDES_AND_CONTROLLER; + + static { + Set> includes = new LinkedHashSet<>(DEFAULT_INCLUDES); + includes.add(Controller.class); + DEFAULT_INCLUDES_AND_CONTROLLER = Collections.unmodifiableSet(includes); + } + + private final Class[] controllers; + + GraphQlTypeExcludeFilter(Class testClass) { + super(testClass); + this.controllers = getAnnotation().getValue("controllers", Class[].class).orElse(NO_CONTROLLERS); + } + + @Override + protected Set> getDefaultIncludes() { + if (ObjectUtils.isEmpty(this.controllers)) { + return DEFAULT_INCLUDES_AND_CONTROLLER; + } + return DEFAULT_INCLUDES; + } + + @Override + protected Set> getComponentIncludes() { + return new LinkedHashSet<>(Arrays.asList(this.controllers)); + } +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureGraphQlTester.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureWebGraphQlTester.java similarity index 81% rename from graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureGraphQlTester.java rename to graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureWebGraphQlTester.java index 34e23ebe..b59366a2 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureGraphQlTester.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureWebGraphQlTester.java @@ -24,20 +24,21 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; /** - * Annotation that can be applied to a test class to enable a {@link GraphQlTester}. + * Annotation that can be applied to a test class to enable a {@link WebGraphQlTester}. * * @author Brian Clozel - * @see GraphQlTesterAutoConfiguration * @since 1.0.0 + * @see WebTestClientMockMvcAutoConfiguration + * @see WebGraphQlTesterAutoConfiguration */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @ImportAutoConfiguration -public @interface AutoConfigureGraphQlTester { +public @interface AutoConfigureWebGraphQlTester { } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterConfiguration.java similarity index 64% rename from graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfiguration.java rename to graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterConfiguration.java index 4e1f5e51..45eec4c1 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterConfiguration.java @@ -16,37 +16,35 @@ package org.springframework.graphql.boot.test.tester; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.graphql.boot.GraphQlProperties; -import org.springframework.graphql.test.tester.GraphQlTester; import org.springframework.graphql.test.tester.WebGraphQlTester; import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.client.WebClient; /** - * Auto-configuration for {@link GraphQlTester} in mock environments. + * Configuration classes for {@link WebGraphQlTester} + *

+ * Those should be {@code @Import} in a regular auto-configuration class to guarantee + * their order of execution. * * @author Brian Clozel - * @since 1.0.0 */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ WebClient.class, WebTestClient.class, GraphQlTester.class }) -@AutoConfigureAfter(value = WebTestClientMockMvcAutoConfiguration.class, - name = "org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration") -public class GraphQlTesterAutoConfiguration { +class GraphQlTesterConfiguration { @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({WebClient.class, WebTestClient.class}) @ConditionalOnBean(WebTestClient.class) - public static class WebTestClientGraphQlTesterConfiguration { + @ConditionalOnMissingBean(WebGraphQlTester.class) + public static class WebTestClientConfig { @Bean - public WebGraphQlTester clientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { + public WebGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build(); return WebGraphQlTester.create(mutatedWebTestClient); } @@ -54,13 +52,13 @@ public class GraphQlTesterAutoConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(WebTestClient.class) @ConditionalOnBean(WebGraphQlHandler.class) - public static class WebGraphQlHandlerGraphQlTesterConfiguration { + @ConditionalOnMissingBean({WebGraphQlTester.class}) + public static class WebGraphQlHandlerConfig { @Bean - public WebGraphQlTester handlerGraphQlTester(WebGraphQlHandler handler) { - return WebGraphQlTester.create(handler); + public WebGraphQlTester webGraphQlTester(WebGraphQlHandler webGraphQlHandler) { + return WebGraphQlTester.create(webGraphQlHandler); } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebGraphQlTesterAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebGraphQlTesterAutoConfiguration.java new file mode 100644 index 00000000..9f48311b --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebGraphQlTesterAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-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.boot.test.tester; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +/** + * Auto-configuration for {@link WebGraphQlTester}. + * + * @author Brian Clozel + * @since 1.0.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({GraphQlTester.class}) +@AutoConfigureAfter(value = WebTestClientMockMvcAutoConfiguration.class, + name = "org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration") +@Import({GraphQlTesterConfiguration.WebTestClientConfig.class, GraphQlTesterConfiguration.WebGraphQlHandlerConfig.class}) +public class WebGraphQlTesterAutoConfiguration { + +} diff --git a/graphql-spring-boot-starter/src/main/resources/META-INF/spring.factories b/graphql-spring-boot-starter/src/main/resources/META-INF/spring.factories index 6b72dc70..4a0a2caa 100644 --- a/graphql-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/graphql-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,6 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.graphql.boot.GraphQlAutoConfiguration,\ -org.springframework.graphql.boot.GraphQlServiceAutoConfiguration,\ +org.springframework.graphql.boot.WebGraphQlHandlerAutoConfiguration,\ org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration,\ org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration,\ org.springframework.graphql.boot.actuate.metrics.GraphQlMetricsAutoConfiguration,\ @@ -14,10 +14,17 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.graphql.boot.InvalidSchemaLocationsExceptionFailureAnalyzer # Spring Test @AutoConfigureGraphQlTester -org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester=\ +org.springframework.graphql.boot.test.tester.AutoConfigureWebGraphQlTester=\ org.springframework.graphql.boot.test.tester.WebTestClientMockMvcAutoConfiguration,\ -org.springframework.graphql.boot.test.tester.GraphQlTesterAutoConfiguration +org.springframework.graphql.boot.test.tester.WebGraphQlTesterAutoConfiguration # Spring Test ContextCustomizerFactories org.springframework.test.context.ContextCustomizerFactory=\ -org.springframework.graphql.boot.test.tester.GraphQlTesterContextCustomizerFactory \ No newline at end of file +org.springframework.graphql.boot.test.tester.GraphQlTesterContextCustomizerFactory + +# AutoConfigureGraphQl auto-configuration imports +org.springframework.graphql.boot.test.AutoConfigureGraphQl=\ +org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ +org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ +org.springframework.graphql.boot.GraphQlAutoConfiguration,\ +org.springframework.graphql.boot.WebGraphQlHandlerAutoConfiguration \ No newline at end of file diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java index 253de7c9..4206f384 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfigurationTests.java @@ -46,7 +46,7 @@ class GraphQlWebFluxAutoConfigurationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class, CodecsAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class, - GraphQlServiceAutoConfiguration.class, GraphQlWebFluxAutoConfiguration.class)) + WebGraphQlHandlerAutoConfiguration.class, GraphQlWebFluxAutoConfiguration.class)) .withUserConfiguration(DataFetchersConfiguration.class, CustomWebInterceptor.class) .withPropertyValues( "spring.main.web-application-type=reactive", diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java index 4ab472d2..9340a56c 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfigurationTests.java @@ -50,7 +50,7 @@ class GraphQlWebMvcAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class, - GraphQlServiceAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class)) + WebGraphQlHandlerAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class)) .withUserConfiguration(DataFetchersConfiguration.class, CustomWebInterceptor.class) .withPropertyValues( "spring.main.web-application-type=servlet", diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfigurationTests.java new file mode 100644 index 00000000..f2a95537 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/WebGraphQlHandlerAutoConfigurationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020-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.boot; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer; +import org.springframework.graphql.execution.BatchLoaderRegistry; +import org.springframework.graphql.execution.GraphQlSource; +import org.springframework.graphql.web.WebGraphQlHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link WebGraphQlHandlerAutoConfiguration}. + * + * @author Brian Clozel + */ +class WebGraphQlHandlerAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WebGraphQlHandlerAutoConfiguration.class)) + .withUserConfiguration(GraphQlSourceConfiguration.class); + + @Test + void shouldContributeStandardBeans() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(BatchLoaderRegistry.class); + assertThat(context).hasSingleBean(GraphQlService.class); + assertThat(context).hasSingleBean(AnnotatedControllerConfigurer.class); + assertThat(context).hasSingleBean(WebGraphQlHandler.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class GraphQlSourceConfiguration { + + @Bean + GraphQlSource graphQlSource() { + return mock(GraphQlSource.class); + } + } + +} \ No newline at end of file diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebFluxSecurityAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebFluxSecurityAutoConfigurationTests.java index dcd92526..952984af 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebFluxSecurityAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebFluxSecurityAutoConfigurationTests.java @@ -35,7 +35,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.graphql.boot.Book; import org.springframework.graphql.boot.GraphQlAutoConfiguration; import org.springframework.graphql.boot.GraphQlDataFetchers; -import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration; +import org.springframework.graphql.boot.WebGraphQlHandlerAutoConfiguration; import org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.execution.RuntimeWiringConfigurer; @@ -66,7 +66,7 @@ class GraphQlWebFluxSecurityAutoConfigurationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class, CodecsAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class, - GraphQlServiceAutoConfiguration.class, GraphQlWebFluxAutoConfiguration.class, + WebGraphQlHandlerAutoConfiguration.class, GraphQlWebFluxAutoConfiguration.class, GraphQlWebFluxSecurityAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class)) .withUserConfiguration(DataFetchersConfiguration.class, SecurityConfig.class) .withPropertyValues( diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebMvcSecurityAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebMvcSecurityAutoConfigurationTests.java index 9de3e4b2..15fe96f6 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebMvcSecurityAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/security/GraphQlWebMvcSecurityAutoConfigurationTests.java @@ -31,7 +31,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.graphql.boot.Book; import org.springframework.graphql.boot.GraphQlAutoConfiguration; import org.springframework.graphql.boot.GraphQlDataFetchers; -import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration; +import org.springframework.graphql.boot.WebGraphQlHandlerAutoConfiguration; import org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.execution.RuntimeWiringConfigurer; @@ -71,7 +71,7 @@ class GraphQlWebMvcSecurityAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class, - GraphQlServiceAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class, + WebGraphQlHandlerAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class, GraphQlWebMvcSecurityAutoConfiguration.class, SecurityAutoConfiguration.class)) .withUserConfiguration(DataFetchersConfiguration.class, SecurityConfig.class) .withPropertyValues( diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/BookController.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/BookController.java new file mode 100644 index 00000000..f3ee7492 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/BookController.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-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.boot.test; + +import reactor.core.publisher.Mono; + +import org.springframework.graphql.boot.Book; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +/** + * Example {@code @Controller} to be tested with {@link GraphQlTest @GraphQlTest}. + * + * @author Brian Clozel + */ +@Controller +public class BookController { + + @QueryMapping + public Book bookById(@Argument String id) { + return new Book("42", "Sample Book", 100, "Jane Spring"); + } +} diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/ExampleGraphQlApplication.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/ExampleGraphQlApplication.java new file mode 100644 index 00000000..84b188f8 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/ExampleGraphQlApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020-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.boot.test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Example {@link SpringBootApplication @SpringBootApplication} used with + * {@link GraphQlTest @GraphQlTest} tests. + * + * @author Brian Clozel + */ +@SpringBootApplication +public class ExampleGraphQlApplication { + +} diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTestIntegrationTest.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTestIntegrationTest.java new file mode 100644 index 00000000..6799a8f7 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTestIntegrationTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-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.boot.test; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +/** + * Integration test for {@link GraphQlTest @GraphQlTest} annotated tests. + * + * @author Brian Clozel + */ +@GraphQlTest(controllers = BookController.class, + properties = {"spring.graphql.schema.locations:classpath:books/"}) +public class GraphQlTestIntegrationTest { + + @Autowired + private WebGraphQlTester graphQlTester; + + @Test + void getBookdByIdShouldReturnTestBook() { + String query = "{" + + " bookById(id: \"book-1\"){ " + + " id" + + " name" + + " pageCount" + + " author" + + " }" + + "}"; + graphQlTester.query(query).execute() + .path("data.bookById.id").entity(String.class).isEqualTo("42"); + } + +} diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilterTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilterTests.java new file mode 100644 index 00000000..67bc9b1d --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/GraphQlTypeExcludeFilterTests.java @@ -0,0 +1,184 @@ +/* + * Copyright 2020-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.boot.test; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import graphql.schema.idl.RuntimeWiring; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; +import org.springframework.graphql.web.WebGraphQlHandler; +import org.springframework.graphql.web.WebInput; +import org.springframework.graphql.web.WebInterceptor; +import org.springframework.graphql.web.WebInterceptorChain; +import org.springframework.graphql.web.WebOutput; +import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * Tests for {@link GraphQlTypeExcludeFilter} + * + * @author Brian Clozel + */ +class GraphQlTypeExcludeFilterTests { + + private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + + @Test + void matchWhenHasNoControllers() throws Exception { + GraphQlTypeExcludeFilter filter = new GraphQlTypeExcludeFilter(WithNoControllers.class); + assertThat(excludes(filter, Controller1.class)).isFalse(); + assertThat(excludes(filter, Controller2.class)).isFalse(); + assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse(); + assertThat(excludes(filter, ExampleService.class)).isTrue(); + assertThat(excludes(filter, ExampleRepository.class)).isTrue(); + assertThat(excludes(filter, ExampleWebInterceptor.class)).isFalse(); + assertThat(excludes(filter, ExampleModule.class)).isFalse(); + } + + @Test + void matchWhenHasController() throws Exception { + GraphQlTypeExcludeFilter filter = new GraphQlTypeExcludeFilter(WithController.class); + assertThat(excludes(filter, Controller1.class)).isFalse(); + assertThat(excludes(filter, Controller2.class)).isTrue(); + assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse(); + assertThat(excludes(filter, ExampleService.class)).isTrue(); + assertThat(excludes(filter, ExampleRepository.class)).isTrue(); + assertThat(excludes(filter, ExampleWebInterceptor.class)).isFalse(); + assertThat(excludes(filter, ExampleModule.class)).isFalse(); + } + + @Test + void matchNotUsingDefaultFilters() throws Exception { + GraphQlTypeExcludeFilter filter = new GraphQlTypeExcludeFilter(NotUsingDefaultFilters.class); + assertThat(excludes(filter, Controller1.class)).isTrue(); + assertThat(excludes(filter, Controller2.class)).isTrue(); + assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isTrue(); + assertThat(excludes(filter, ExampleService.class)).isTrue(); + assertThat(excludes(filter, ExampleRepository.class)).isTrue(); + assertThat(excludes(filter, ExampleWebInterceptor.class)).isTrue(); + assertThat(excludes(filter, ExampleModule.class)).isTrue(); + } + + @Test + void matchWithIncludeFilter() throws Exception { + GraphQlTypeExcludeFilter filter = new GraphQlTypeExcludeFilter(WithIncludeFilter.class); + assertThat(excludes(filter, Controller1.class)).isFalse(); + assertThat(excludes(filter, Controller2.class)).isFalse(); + assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse(); + assertThat(excludes(filter, ExampleService.class)).isTrue(); + assertThat(excludes(filter, ExampleRepository.class)).isFalse(); + assertThat(excludes(filter, ExampleWebInterceptor.class)).isFalse(); + assertThat(excludes(filter, ExampleModule.class)).isFalse(); + } + + @Test + void matchWithExcludeFilter() throws Exception { + GraphQlTypeExcludeFilter filter = new GraphQlTypeExcludeFilter(WithExcludeFilter.class); + assertThat(excludes(filter, Controller1.class)).isTrue(); + assertThat(excludes(filter, Controller2.class)).isFalse(); + assertThat(excludes(filter, ExampleRuntimeWiringConfigurer.class)).isFalse(); + assertThat(excludes(filter, ExampleService.class)).isTrue(); + assertThat(excludes(filter, ExampleRepository.class)).isTrue(); + assertThat(excludes(filter, ExampleWebInterceptor.class)).isFalse(); + assertThat(excludes(filter, ExampleModule.class)).isFalse(); + } + + private boolean excludes(GraphQlTypeExcludeFilter filter, Class type) throws IOException { + MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(type.getName()); + return filter.match(metadataReader, this.metadataReaderFactory); + } + + @GraphQlTest + static class WithNoControllers { + + } + + @GraphQlTest(Controller1.class) + static class WithController { + + } + + @GraphQlTest(useDefaultFilters = false) + static class NotUsingDefaultFilters { + + } + + @GraphQlTest(includeFilters = @ComponentScan.Filter(Repository.class)) + static class WithIncludeFilter { + + } + + @GraphQlTest(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Controller1.class)) + static class WithExcludeFilter { + + } + + @Controller + static class Controller1 { + + } + + @Controller + static class Controller2 { + + } + + @Service + static class ExampleService { + + } + + @Repository + static class ExampleRepository { + + } + + static class ExampleRuntimeWiringConfigurer implements RuntimeWiringConfigurer { + @Override + public void configure(RuntimeWiring.Builder builder) { + + } + } + + static class ExampleWebInterceptor implements WebInterceptor { + + @Override + public Mono intercept(WebInput webInput, WebInterceptorChain chain) { + return null; + } + + } + + @SuppressWarnings("serial") + static class ExampleModule extends SimpleModule { + + } + +} \ No newline at end of file diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfigurationTests.java index 77fe27bd..ae8b173c 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfigurationTests.java @@ -23,7 +23,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.graphql.boot.GraphQlProperties; -import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.server.RouterFunction; @@ -34,20 +34,20 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** - * Tests for {@link GraphQlTesterAutoConfiguration} + * Tests for {@link WebGraphQlTesterAutoConfiguration} * * @author Brian Clozel */ class GraphQlTesterAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(GraphQlTesterAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(WebGraphQlTesterAutoConfiguration.class)); @Test void shouldNotBeConfiguredWithoutGraphQlHandlerNorWebTestClient() { this.contextRunner.run((context) -> { assertThat(context).hasNotFailed(); - assertThat(context).doesNotHaveBean(GraphQlTester.class); + assertThat(context).doesNotHaveBean(WebGraphQlTester.class); }); } @@ -55,7 +55,7 @@ class GraphQlTesterAutoConfigurationTests { void shouldBeConfiguredWhenGraphQlHandlerPresent() { this.contextRunner.withUserConfiguration(HandlerConfiguration.class).run((context) -> { assertThat(context).hasNotFailed(); - assertThat(context).hasSingleBean(GraphQlTester.class); + assertThat(context).hasSingleBean(WebGraphQlTester.class); }); } @@ -63,7 +63,7 @@ class GraphQlTesterAutoConfigurationTests { void shouldBeConfiguredWhenWebTestClientPresent() { this.contextRunner.withUserConfiguration(WebTestClientConfiguration.class).run((context) -> { assertThat(context).hasNotFailed(); - assertThat(context).hasSingleBean(GraphQlTester.class); + assertThat(context).hasSingleBean(WebGraphQlTester.class); }); } diff --git a/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java b/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java index ad99c569..0662ccbe 100644 --- a/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java +++ b/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester; +import org.springframework.graphql.boot.test.tester.AutoConfigureWebGraphQlTester; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.test.tester.WebGraphQlTester; @@ -29,7 +29,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; @SpringBootTest() @AutoConfigureWebTestClient -@AutoConfigureGraphQlTester +@AutoConfigureWebGraphQlTester class SampleApplicationTests { @Autowired diff --git a/samples/webmvc-http-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java b/samples/webmvc-http-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java index 6fc703aa..8885ed50 100644 --- a/samples/webmvc-http-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java +++ b/samples/webmvc-http-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester; +import org.springframework.graphql.boot.test.tester.AutoConfigureWebGraphQlTester; import org.springframework.graphql.execution.ErrorType; import org.springframework.graphql.test.tester.WebGraphQlTester; @@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; @SpringBootTest @AutoConfigureMockMvc -@AutoConfigureGraphQlTester +@AutoConfigureWebGraphQlTester class SampleApplicationTests { @Autowired diff --git a/samples/webmvc-http/src/test/java/io/spring/sample/graphql/project/MockMvcGraphQlTests.java b/samples/webmvc-http/src/test/java/io/spring/sample/graphql/project/MockMvcGraphQlTests.java index 8f90ed40..aa4da998 100644 --- a/samples/webmvc-http/src/test/java/io/spring/sample/graphql/project/MockMvcGraphQlTests.java +++ b/samples/webmvc-http/src/test/java/io/spring/sample/graphql/project/MockMvcGraphQlTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester; +import org.springframework.graphql.boot.test.tester.AutoConfigureWebGraphQlTester; import org.springframework.graphql.test.tester.GraphQlTester; import org.springframework.test.web.servlet.MockMvc; @@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @SpringBootTest @AutoConfigureMockMvc -@AutoConfigureGraphQlTester +@AutoConfigureWebGraphQlTester public class MockMvcGraphQlTests { @Autowired diff --git a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc index 59fbe4a5..d950142c 100644 --- a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc @@ -119,7 +119,7 @@ spring.graphql.schema.printer.enabled=false The GraphQL Java `RuntimeWiring.Builder` can be used to register ``DataFetcher``s, type resolvers, custom scalar types, and more. You can declare `RuntimeWiringConfigurer` beans in your Spring config to get access to the `RuntimeWiring.Builder`. The Boot -starter detects such beans adds them to <>. +starter detects such beans and adds them to <>. Typically, however, applications will not implement ``DataFetcher`` directly and will instead create <>. The Boot @@ -364,8 +364,12 @@ A GraphQL error metric counter is available at `/actuator/metrics/graphql.error` [[boot-graphql-testing]] == Testing -For Spring GraphQL testing support, add the below to your classpath and that will make -a `WebGraphQlTester` available for injection into tests: +Spring GraphQL offers many ways to test your application: with or without a live server, using the transport +or testing directly the engine. You'll be using a lot the <>, +so make sure you're familiar with it before writing your first test. + +The Spring Boot starter will help you and configure the testing infrastructure; all you to start +is to add the following to your classpath: [source,groovy,indent=0,subs="verbatim,quotes,attributes",role="primary"] .Gradle @@ -374,7 +378,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.graphql:spring-graphql-test:{spring-graphql-version}' - // Also add this, unless `spring-boot-starter-webflux` is also present + // Also add this, unless spring-boot-starter-webflux is also present testImplementation 'org.springframework:spring-webflux' // ... @@ -432,6 +436,89 @@ repositories { ---- +In the next sections, we'll see the various options available for testing your Spring GraphQL application. + +[[boot-graphql-testing-graphqltest]] +=== Testing GraphQL components only + +You can test your Spring GraphQL `@Controller` and GraphQL components with the `@GraphQlTest` annotation. +`@GraphQlTest` auto-configures the Spring GraphQL infrastructure ad limits scanned beans to `@Controller`, +`RuntimeWiringConfigurer`, `JsonComponent`, `WebInterceptor`, `Converter`, `GenericConverter` +Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@GraphQlTest` annotation is used. +`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans. + +This arrangement is quite similar to the +{spring-boot-ref-docs}/features.html#features.testing.spring-boot-applications.spring-mvc-tests[@WevMvcTest support], +except that the web framework of choice (Spring MVC or Spring WebFlux) is not involved at all as requests are performed +directly against the `WebGraphQlHandler`. + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@GraphQlTest(controllers = BookController.class) +public class BookControllerTests { + + @Autowired + private WebGraphQlTester graphQlTester; + + @MockBean + private BookRepository bookRepository; + + @Test + void bookdByIdShouldReturnSpringBook() { + given(this.bookRepository.findById(42L)).willReturn(new Book(42L, "Spring GraphQL")); + String query = // + graphQlTester.query(query).execute() + .path("data.bookById.name").entity(String.class).isEqualTo("Spring GraphQL"); + } + +} +---- + +This mode is also useful for testing subscriptions without involving the transport protocol. + +[source,java,indent=0,subs="verbatim,quotes"] +---- +@GraphQlTest(controllers = GreetingController.class) +public class GreetingControllerTests { + + @Autowired + private WebGraphQlTester graphQlTester; + + @Test + void subscription() { + Flux result = this.graphQlTester.query("subscription { greetings }") + .executeSubscription() + .toFlux("greetings", String.class); + + // Use StepVerifier from "reactor-test" to verify the stream... + StepVerifier.create(result) + .expectNext("Hi") + .expectNext("Bonjour") + .expectNext("Hola") + .verifyComplete(); + } + +} +---- + +The above subscription test is performed directly against the `WebGraphQlHandler` that +both HTTP and WebSocket transports delegate to. It passes through the `WebInterceptor` +chain and then calls GraphQL Java which returns a Reactive Streams `Publisher`. + +[NOTE] +.Testing multiple controllers with `@GraphQlTest` +==== +Because GraphQL is not about REST endpoints but navigating relations in an object graph, +multiple `@Controller` components can be involved in a single query. +For this case, `@GraphQlTest` supports testing multiple controllers with its `controllers` annotation attribute. +==== + +[[boot-graphql-testing-mock]] +=== Testing the HTTP transport with a Mock server + +If your test requires more integration with application components, you can choose to test the entire +application and involve the transport layers and the Web framework. + For GraphQL over HTTP with Spring MVC, using `MockMvc` as the server: [source,java,indent=0,subs="verbatim,quotes"] @@ -463,13 +550,18 @@ public class MockMvcGraphQlTests { } ---- -For GraphQL over HTTP with a -{spring-boot-ref-docs}/features.html#features.testing.spring-boot-applications.with-running-server[running server]: + + +[[boot-graphql-testing-live]] +=== Testing the HTTP transport with a live server + +You can also run tests against the full application infrastructure, including a live server. +Just like {spring-boot-ref-docs}/features.html#features.testing.spring-boot-applications.with-running-server[REST endpoints testing], +you can use a `WebEnvironment.RANDOM_PORT` environment and test queries using `WebGraphQlTester`. [source,java,indent=0,subs="verbatim,quotes"] ---- @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureGraphQlTester public class MockMvcGraphQlTests { @Autowired @@ -477,35 +569,3 @@ public class MockMvcGraphQlTests { } ---- - -Subscriptions can be tested without WebSocket as shown below: - -[source,java,indent=0,subs="verbatim,quotes"] ----- -@SpringBootTest -@AutoConfigureGraphQlTester -public class MockMvcGraphQlTests { - - @Autowired - private WebGraphQlTester graphQlTester; - - @Test - void subscription() { - Flux result = this.graphQlTester.query("subscription { greetings }") - .executeSubscription() - .toFlux("greetings", String.class); - - // Use StepVerifier from "reactor-test" to verify the stream... - StepVerifier.create(result) - .expectNext("Hi") - .expectNext("Bonjour") - .expectNext("Hola") - .verifyComplete(); - } - -} ----- - -The above subscription test is performed directly against the `WebGraphQlHandler` that -both HTTP and WebSocket transports delegate to. It passes through the `WebInterceptor` -chain and then calls GraphQL Java which returns a Reactive Streams `Publisher`. diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebGraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebGraphQlTester.java index 969e065a..7942012d 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebGraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebGraphQlTester.java @@ -24,8 +24,8 @@ import org.springframework.test.web.reactive.server.WebTestClient; /** * Main entry point for testing GraphQL over a Web transport with requests * executed {@link #create(WebTestClient) via WebTestClient} or - * {@link #create(WebGraphQlHandler) via WebGraphQlHandler}. See the below for - * examples with different scenarios. + * {@link #create(WebGraphQlHandler) via WebGraphQlHandler}. + * * *

* GraphQL requests to Spring MVC without an HTTP server: