From c0707e4f5bf548f2356b5753287be963009127dc Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 21 Dec 2021 08:34:06 +0100 Subject: [PATCH] Auto-configure GraphQlTester This commit adds the required infrastructure to auto-configure a `GraphQlTester` or `WebGraphQlTester` in Spring Boot tests. Specific annotations like `AutoConfigureGraphQlTester` and `AutoConfigureWebGraphQlTester` will contribute pre-configured beans for testing a GraphQL with the tester. This also ships a `ContextCustomize` for contributing a `GraphQlTester` in the case of a full `@SpringBootTest` integration test against a live server. See gh-29140 --- .../GraphQlTestContextBootstrapper.java | 36 +++ .../tester/AutoConfigureGraphQlTester.java | 43 ++++ .../tester/AutoConfigureWebGraphQlTester.java | 52 +++++ .../GraphQlTesterAutoConfiguration.java | 49 ++++ .../WebGraphQlTesterAutoConfiguration.java | 51 ++++ .../graphql/tester/package-info.java | 20 ++ .../main/resources/META-INF/spring.factories | 8 + .../GraphQlTesterAutoConfigurationTests.java | 62 +++++ .../spring-boot-test/build.gradle | 1 + .../GraphQlTesterContextCustomizer.java | 218 ++++++++++++++++++ ...GraphQlTesterContextCustomizerFactory.java | 54 +++++ .../test/graphql/tester/package-info.java | 20 ++ .../main/resources/META-INF/spring.factories | 1 + ...sterContextCustomizerIntegrationTests.java | 88 +++++++ ...textCustomizerWithCustomBasePathTests.java | 88 +++++++ ...tCustomizerWithCustomContextPathTests.java | 80 +++++++ 16 files changed, 871 insertions(+) create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestContextBootstrapper.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureGraphQlTester.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/package-info.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizer.java create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerFactory.java create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/package-info.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomBasePathTests.java create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomContextPathTests.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestContextBootstrapper.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestContextBootstrapper.java new file mode 100644 index 00000000000..5e05495cde5 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/GraphQlTestContextBootstrapper.java @@ -0,0 +1,36 @@ +/* + * 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.boot.test.autoconfigure.graphql; + +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/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureGraphQlTester.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureGraphQlTester.java new file mode 100644 index 00000000000..f940d3ed02d --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureGraphQlTester.java @@ -0,0 +1,43 @@ +/* + * 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.boot.test.autoconfigure.graphql.tester; + +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; +import org.springframework.graphql.test.tester.GraphQlTester; + +/** + * Annotation that can be applied to a test class to enable a {@link GraphQlTester}. + * + * @author Brian Clozel + * @since 2.7.0 + * @see GraphQlTesterAutoConfiguration + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ImportAutoConfiguration +public @interface AutoConfigureGraphQlTester { + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java new file mode 100644 index 00000000000..5c5a8e1bd6b --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/AutoConfigureWebGraphQlTester.java @@ -0,0 +1,52 @@ +/* + * 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.boot.test.autoconfigure.graphql.tester; + +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; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.graphql.test.tester.WebGraphQlTester; + +/** + * Annotation that can be applied to a test class to enable a {@link WebGraphQlTester}. + * + *

+ * This annotation should be used with + * {@link org.springframework.boot.test.context.SpringBootTest @SpringBootTest} tests with + * Spring MVC or Spring WebFlux mock infrastructures. + * + * @author Brian Clozel + * @since 2.7.0 + * @see WebGraphQlTesterAutoConfiguration + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@AutoConfigureMockMvc +@AutoConfigureWebTestClient +@ImportAutoConfiguration +public @interface AutoConfigureWebGraphQlTester { + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java new file mode 100644 index 00000000000..684b3ef6b70 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * 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.boot.test.autoconfigure.graphql.tester; + +import graphql.GraphQL; + +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.boot.autoconfigure.graphql.GraphQlAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.test.tester.GraphQlTester; + +/** + * Auto-configuration for {@link GraphQlTester}. + * + * @author Brian Clozel + * @since 2.7.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ GraphQL.class, GraphQlTester.class }) +@AutoConfigureAfter(GraphQlAutoConfiguration.class) +public class GraphQlTesterAutoConfiguration { + + @Bean + @ConditionalOnBean(GraphQlService.class) + @ConditionalOnMissingBean + public GraphQlTester graphQlTester(GraphQlService graphQlService) { + return GraphQlTester.create(graphQlService); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java new file mode 100644 index 00000000000..e2f4fa469fd --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/WebGraphQlTesterAutoConfiguration.java @@ -0,0 +1,51 @@ +/* + * 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.boot.test.autoconfigure.graphql.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.boot.autoconfigure.graphql.GraphQlProperties; +import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Auto-configuration for {@link WebGraphQlTester}. + * + * @author Brian Clozel + * @since 2.7.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ WebClient.class, WebTestClient.class, WebGraphQlTester.class }) +@AutoConfigureAfter({ WebTestClientAutoConfiguration.class, MockMvcAutoConfiguration.class }) +public class WebGraphQlTesterAutoConfiguration { + + @Bean + @ConditionalOnBean(WebTestClient.class) + @ConditionalOnMissingBean + public WebGraphQlTester webTestClientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { + WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build(); + return WebGraphQlTester.create(mutatedWebTestClient); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/package-info.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/package-info.java new file mode 100644 index 00000000000..182468fa0e4 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-configuration for GraphQL tester. + */ +package org.springframework.boot.test.autoconfigure.graphql.tester; diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index 26149a57fe3..0f5028ae3fe 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -194,6 +194,14 @@ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration +# AutoConfigureGraphQlTester auto-configuration imports +org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester=\ +org.springframework.boot.test.autoconfigure.graphql.tester.GraphQlTesterAutoConfiguration + +# AutoConfigureWebGraphQlTester auto-configuration imports +org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureWebGraphQlTester=\ +org.springframework.boot.test.autoconfigure.graphql.tester.WebGraphQlTesterAutoConfiguration + # AutoConfigureWebServiceClient org.springframework.boot.test.autoconfigure.webservices.client.AutoConfigureWebServiceClient=\ org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTemplateAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfigurationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfigurationTests.java new file mode 100644 index 00000000000..322902f4306 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfigurationTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-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.boot.test.autoconfigure.graphql.tester; + +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.test.tester.GraphQlTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link GraphQlTesterAutoConfiguration}. + * + * @author Brian Clozel + */ +class GraphQlTesterAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(GraphQlTesterAutoConfiguration.class)); + + @Test + void shouldNotContributeTesterIfGraphQlServiceNotPresent() { + this.contextRunner.run((context) -> assertThat(context).hasNotFailed().doesNotHaveBean(GraphQlTester.class)); + } + + @Test + void shouldContributeTester() { + this.contextRunner.withUserConfiguration(CustomGraphQlServiceConfiguration.class) + .run((context) -> assertThat(context).hasNotFailed().hasSingleBean(GraphQlTester.class)); + } + + @Configuration(proxyBeanMethods = false) + static class CustomGraphQlServiceConfiguration { + + @Bean + GraphQlService graphQlService() { + return mock(GraphQlService.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/build.gradle b/spring-boot-project/spring-boot-test/build.gradle index 7571c2b34f9..8ce8eba2c82 100644 --- a/spring-boot-project/spring-boot-test/build.gradle +++ b/spring-boot-project/spring-boot-test/build.gradle @@ -36,6 +36,7 @@ dependencies { optional("org.springframework:spring-test") optional("org.springframework:spring-web") optional("org.springframework:spring-webflux") + optional("org.springframework.graphql:spring-graphql-test") optional("net.sourceforge.htmlunit:htmlunit") { exclude(group: "commons-logging", module: "commons-logging") } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizer.java new file mode 100644 index 00000000000..d079bd5b76d --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizer.java @@ -0,0 +1,218 @@ +/* + * 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.boot.test.graphql.tester; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.Ordered; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.test.tester.WebGraphQlTester; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContextAnnotationUtils; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.context.WebApplicationContext; + +/** + * {@link ContextCustomizer} for {@link GraphQlTester}. + * + * @author Brian Clozel + */ +class GraphQlTesterContextCustomizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(mergedConfig.getTestClass(), + SpringBootTest.class); + if (springBootTest.webEnvironment().isEmbedded()) { + registerGraphQlTester(context); + } + } + + private void registerGraphQlTester(ConfigurableApplicationContext context) { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + if (beanFactory instanceof BeanDefinitionRegistry) { + registerGraphQlTester((BeanDefinitionRegistry) beanFactory); + } + } + + private void registerGraphQlTester(BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(GraphQlTesterRegistrar.class); + definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(GraphQlTesterRegistrar.class.getName(), definition); + } + + @Override + public boolean equals(Object obj) { + return (obj != null) && (obj.getClass() == getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + private static class GraphQlTesterRegistrar + implements BeanDefinitionRegistryPostProcessor, Ordered, BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory, + GraphQlTester.class, false, false).length == 0) { + registry.registerBeanDefinition(WebGraphQlTester.class.getName(), + new RootBeanDefinition(GraphQlTesterFactory.class)); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 1; + } + + } + + public static class GraphQlTesterFactory implements FactoryBean, ApplicationContextAware { + + private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; + + private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; + + private ApplicationContext applicationContext; + + private GraphQlTester object; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public Class getObjectType() { + return GraphQlTester.class; + } + + @Override + public GraphQlTester getObject() throws Exception { + if (this.object == null) { + this.object = createGraphQlTester(); + } + return this.object; + } + + private WebGraphQlTester createGraphQlTester() { + WebTestClient webTestClient = this.applicationContext.getBean(WebTestClient.class); + boolean sslEnabled = isSslEnabled(this.applicationContext); + String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080"); + WebTestClient mutatedWebClient = webTestClient.mutate().baseUrl(getBaseUrl(sslEnabled, port)).build(); + return WebGraphQlTester.create(mutatedWebClient); + } + + private String getBaseUrl(boolean sslEnabled, String port) { + String basePath = deduceBasePath(); + return (sslEnabled ? "https" : "http") + "://localhost:" + port + basePath; + } + + private String deduceBasePath() { + return deduceServerBasePath() + findConfiguredGraphQlPath(); + } + + private String findConfiguredGraphQlPath() { + String configuredPath = this.applicationContext.getEnvironment().getProperty("spring.graphql.path"); + return StringUtils.hasText(configuredPath) ? configuredPath : "/graphql"; + } + + private String deduceServerBasePath() { + String serverBasePath = ""; + WebApplicationType webApplicationType = deduceFromApplicationContext(this.applicationContext.getClass()); + if (webApplicationType == WebApplicationType.REACTIVE) { + serverBasePath = this.applicationContext.getEnvironment().getProperty("spring.webflux.base-path"); + + } + else if (webApplicationType == WebApplicationType.SERVLET) { + serverBasePath = ((WebApplicationContext) this.applicationContext).getServletContext().getContextPath(); + } + return (serverBasePath != null) ? serverBasePath : ""; + } + + static WebApplicationType deduceFromApplicationContext(Class applicationContextClass) { + if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { + return WebApplicationType.SERVLET; + } + if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) { + return WebApplicationType.REACTIVE; + } + return WebApplicationType.NONE; + } + + private static boolean isAssignable(String target, Class type) { + try { + return ClassUtils.resolveClassName(target, null).isAssignableFrom(type); + } + catch (Throwable ex) { + return false; + } + } + + private boolean isSslEnabled(ApplicationContext context) { + try { + AbstractConfigurableWebServerFactory webServerFactory = context + .getBean(AbstractConfigurableWebServerFactory.class); + return webServerFactory.getSsl() != null && webServerFactory.getSsl().isEnabled(); + } + catch (NoSuchBeanDefinitionException ex) { + return false; + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerFactory.java new file mode 100644 index 00000000000..497db17727a --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerFactory.java @@ -0,0 +1,54 @@ +/* + * 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.boot.test.graphql.tester; + +import java.util.List; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.TestContextAnnotationUtils; +import org.springframework.util.ClassUtils; + +/** + * {@link ContextCustomizerFactory} for {@link GraphQlTester}. + * + * @author Brian Clozel + * @see GraphQlTesterContextCustomizer + */ +class GraphQlTesterContextCustomizerFactory implements ContextCustomizerFactory { + + private static final String GRAPHQLTESTER_CLASS = "org.springframework.graphql.test.tester.GraphQlTester"; + + private static final String WEBTESTCLIENT_CLASS = "org.springframework.test.web.reactive.server.WebTestClient"; + + @Override + public ContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + SpringBootTest springBootTest = TestContextAnnotationUtils.findMergedAnnotation(testClass, + SpringBootTest.class); + return (springBootTest != null && isGraphQlTesterPresent()) ? new GraphQlTesterContextCustomizer() : null; + } + + private boolean isGraphQlTesterPresent() { + return ClassUtils.isPresent(WEBTESTCLIENT_CLASS, getClass().getClassLoader()) + && ClassUtils.isPresent(GRAPHQLTESTER_CLASS, getClass().getClassLoader()); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/package-info.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/package-info.java new file mode 100644 index 00000000000..9eeb1f664df --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/graphql/tester/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * {@link org.springframework.graphql.test.tester.GraphQlTester} utilities. + */ +package org.springframework.boot.test.graphql.tester; diff --git a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories index 5a75a417aa1..3dd1783cd91 100644 --- a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -2,6 +2,7 @@ org.springframework.test.context.ContextCustomizerFactory=\ org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ +org.springframework.boot.test.graphql.tester.GraphQlTesterContextCustomizerFactory,\ org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ org.springframework.boot.test.web.client.TestRestTemplateContextCustomizerFactory,\ diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerIntegrationTests.java new file mode 100644 index 00000000000..b9c334ca889 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerIntegrationTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-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.boot.test.graphql.tester; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ContextPathCompositeHandler; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.test.annotation.DirtiesContext; + +/** + * Integration test for {@link GraphQlTesterContextCustomizer}. + * + * @author Brian Clozel + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.main.web-application-type=reactive") +@DirtiesContext +class GraphQlTesterContextCustomizerIntegrationTests { + + @Autowired + GraphQlTester graphQlTester; + + @Test + void shouldHandleGraphQlRequests() { + this.graphQlTester.query("{}").executeAndVerify(); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + @Bean + HttpHandler httpHandler() { + TestHandler httpHandler = new TestHandler(); + Map handlersMap = Collections.singletonMap("/graphql", httpHandler); + return new ContextPathCompositeHandler(handlersMap); + } + + } + + static class TestHandler implements HttpHandler { + + private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + response.setStatusCode(HttpStatus.OK); + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + return response.writeWith(Mono.just(factory.wrap("{\"data\":{}}".getBytes()))); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomBasePathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomBasePathTests.java new file mode 100644 index 00000000000..6c1ecb4e199 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomBasePathTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-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.boot.test.graphql.tester; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ContextPathCompositeHandler; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.test.context.TestPropertySource; + +/** + * Tests for {@link GraphQlTesterContextCustomizer} with a custom context path for a + * Reactive web application. + * + * @author Brian Clozel + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = { "spring.main.web-application-type=reactive", "spring.webflux.base-path=/test" }) +class GraphQlTesterContextCustomizerWithCustomBasePathTests { + + @Autowired + GraphQlTester graphQlTester; + + @Test + void shouldHandleGraphQlRequests() { + this.graphQlTester.query("{}").executeAndVerify(); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + @Bean + HttpHandler httpHandler() { + TestHandler httpHandler = new TestHandler(); + Map handlersMap = Collections.singletonMap("/test/graphql", httpHandler); + return new ContextPathCompositeHandler(handlersMap); + } + + } + + static class TestHandler implements HttpHandler { + + private static final DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + response.setStatusCode(HttpStatus.OK); + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + return response.writeWith(Mono.just(factory.wrap("{\"data\":{}}".getBytes()))); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomContextPathTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomContextPathTests.java new file mode 100644 index 00000000000..095e4320414 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/graphql/tester/GraphQlTesterContextCustomizerWithCustomContextPathTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-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.boot.test.graphql.tester; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Tests for {@link GraphQlTesterContextCustomizer} with a custom context path for a + * Servlet web application. + * + * @author Brian Clozel + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "server.servlet.context-path=/test") +class GraphQlTesterContextCustomizerWithCustomContextPathTests { + + @Autowired + GraphQlTester graphQlTester; + + @Test + void shouldHandleGraphQlRequests() { + this.graphQlTester.query("{}").executeAndVerify(); + } + + @Configuration(proxyBeanMethods = false) + @Import(TestController.class) + static class TestConfig { + + @Bean + TomcatServletWebServerFactory webServerFactory() { + TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(0); + factory.setContextPath("/test"); + return factory; + } + + @Bean + DispatcherServlet dispatcherServlet() { + return new DispatcherServlet(); + } + + } + + @RestController + static class TestController { + + @PostMapping(path = "/graphql", produces = MediaType.APPLICATION_JSON_VALUE) + String graphql() { + return "{}"; + } + + } + +}