diff --git a/README.md b/README.md index b4b0c747..e66dad25 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ depending on the type of web application you'd like to build. Once the project i `build.gradle` snippet: ```groovy dependencies { - implementation 'org.springframework.experimental:graphql-spring-boot-starter:0.1.0-SNAPSHOT' + implementation 'org.springframework.experimental:graphql-spring-boot-starter:1.0.0-SNAPSHOT' // Spring Web MVC starter implementation 'org.springframework.boot:spring-boot-starter-web' @@ -39,7 +39,7 @@ repositories { org.springframework.experimental graphql-spring-boot-starter - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT @@ -155,6 +155,42 @@ You can contribute [`WebInterceptor` beans](https://github.com/spring-projects-e to the application context, so as to customize the `ExecutionInput` or the `ExecutionResult` of the query. A custom `WebInterceptor` can, for example, change the HTTP request/response headers. +### Testing support + +When the `spring-boot-starter-test` dependency is on the classpath, Spring GraphQL provides a testing infrastructure for your application. + +Spring Boot allows you to test your web application with [with a mock environment](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-mock-environment) +or [with a running server](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server). +In both cases, adding the `@AutoConfigureGraphQlTester` annotation on your test class will contribute a `GraphQlTester` bean you can inject and use in your tests: + +```` java +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureGraphQlTester +public class MockMvcGraphQlTests { + + @Autowired + private GraphQlTester graphQlTester; + + @Test + void jsonPath() { + String query = "{" + + " project(slug:\"spring-framework\") {" + + " releases {" + + " version" + + " }" + + " }" + + "}"; + + this.graphQlTester.query(query) + .execute() + .path("project.releases[*].version") + .entityList(String.class) + .hasSizeGreaterThan(1); + } +} +```` + ### Metrics If the `spring-boot-starter-actuator` dependency is on the classpath, metrics will be collected for GraphQL requests. diff --git a/graphql-spring-boot-starter/build.gradle b/graphql-spring-boot-starter/build.gradle index 91eb9df2..24f6cef6 100644 --- a/graphql-spring-boot-starter/build.gradle +++ b/graphql-spring-boot-starter/build.gradle @@ -38,6 +38,9 @@ dependencies { compileOnly 'io.micrometer:micrometer-core' compileOnly 'org.springframework.boot:spring-boot-actuator-autoconfigure' + compileOnly project(':spring-graphql-test') + compileOnly 'org.springframework.boot:spring-boot-test' + compileOnly 'com.google.code.findbugs:jsr305:3.0.2' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor' @@ -48,6 +51,7 @@ dependencies { testImplementation 'org.springframework:spring-webmvc' testImplementation 'org.springframework:spring-websocket' testImplementation 'io.projectreactor:reactor-test' + testImplementation 'io.projectreactor.netty:reactor-netty' testImplementation 'javax.servlet:javax.servlet-api' testImplementation 'org.apache.tomcat.embed:tomcat-embed-core' testImplementation 'org.apache.tomcat.embed:tomcat-embed-websocket' 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/AutoConfigureGraphQlTester.java new file mode 100644 index 00000000..ede9f3c0 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/AutoConfigureGraphQlTester.java @@ -0,0 +1,42 @@ +/* + * 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 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 + * @see GraphQlTesterAutoConfiguration + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ImportAutoConfiguration +public @interface AutoConfigureGraphQlTester { + +} 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/GraphQlTesterAutoConfiguration.java new file mode 100644 index 00000000..d13786d4 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfiguration.java @@ -0,0 +1,66 @@ +/* + * 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.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.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. + * + * @author Brian Clozel + */ +@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 { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(WebTestClient.class) + static class WebTestClientGraphQlTesterConfiguration { + + @Bean + public GraphQlTester clientGraphQlTester(WebTestClient webTestClient, GraphQlProperties properties) { + WebTestClient mutatedWebTestClient = webTestClient.mutate().baseUrl(properties.getPath()).build(); + return GraphQlTester.create(mutatedWebTestClient); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(WebTestClient.class) + @ConditionalOnBean(WebGraphQlHandler.class) + static class WebGraphQlHandlerGraphQlTesterConfiguration { + + @Bean + public GraphQlTester handlerGraphQlTester(WebGraphQlHandler handler) { + return GraphQlTester.create(handler); + } + + } + +} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizer.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizer.java new file mode 100644 index 00000000..04929088 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizer.java @@ -0,0 +1,173 @@ +/* + * 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.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.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.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.StringUtils; + +/** + * {@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(GraphQlTesterContextCustomizer.GraphQlTesterRegistrar.class); + definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(GraphQlTesterContextCustomizer.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(GraphQlTester.class.getName(), + new RootBeanDefinition(GraphQlTesterContextCustomizer.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 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 GraphQlTester createGraphQlTester() { + WebTestClient webTestClient = this.applicationContext.getBean(WebTestClient.class); + boolean sslEnabled = isSslEnabled(this.applicationContext); + String port = this.applicationContext.getEnvironment().getProperty("local.server.port", "8080"); + WebTestClient mutatedWebClient = webTestClient.mutate().baseUrl(getBaseUrl(sslEnabled, port)).build(); + return GraphQlTester.create(mutatedWebClient); + } + + private String getBaseUrl(boolean sslEnabled, String port) { + String basePath = deduceBasePath(); + return (sslEnabled ? "https" : "http") + "://localhost:" + port + basePath; + } + + private String deduceBasePath() { + String configuredPath = this.applicationContext.getEnvironment().getProperty("spring.graphql.path"); + return StringUtils.hasText(configuredPath) ? configuredPath : "/graphql"; + } + + 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/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerFactory.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerFactory.java new file mode 100644 index 00000000..2bde54fe --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerFactory.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.graphql.boot.test.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 ContextCustomizer} for {@link GraphQlTester}. + * + * @author Brian Clozel + */ +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/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebTestClientMockMvcAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebTestClientMockMvcAutoConfiguration.java new file mode 100644 index 00000000..5027ad41 --- /dev/null +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/test/tester/WebTestClientMockMvcAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * 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 java.util.List; + +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.test.web.reactive.server.WebTestClientBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.client.MockMvcWebTestClient; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Auto-configuration for {@link WebTestClient} support with {@link MockMvc}. + *

Temporary workaround for upcoming enhancement request in Spring Boot 2.6.0. + * + * @author Brian Clozel + * @see Spring Boot 2.6.x issue + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ WebClient.class, WebTestClient.class, MockMvcWebTestClient.class}) +@AutoConfigureAfter(name = "org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration") +public class WebTestClientMockMvcAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(MockMvc.class) + public WebTestClient webTestClient(MockMvc mockMvc, List customizers) { + WebTestClient.Builder builder = MockMvcWebTestClient.bindTo(mockMvc); + for (WebTestClientBuilderCustomizer customizer : customizers) { + customizer.customize(builder); + } + return builder.build(); + } + +} 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 c2134a89..423ff5fa 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 @@ -4,3 +4,10 @@ org.springframework.graphql.boot.GraphQlAutoConfiguration,\ org.springframework.graphql.boot.GraphQlServiceAutoConfiguration,\ org.springframework.graphql.boot.WebFluxGraphQlAutoConfiguration,\ org.springframework.graphql.boot.WebMvcGraphQlAutoConfiguration +# Spring Test @AutoConfigureGraphQlTester +org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester=\ +org.springframework.graphql.boot.test.tester.WebTestClientMockMvcAutoConfiguration,\ +org.springframework.graphql.boot.test.tester.GraphQlTesterAutoConfiguration +# Spring Test ContextCustomizerFactories +org.springframework.test.context.ContextCustomizerFactory=\ +org.springframework.graphql.boot.test.tester.GraphQlTesterContextCustomizerFactory \ 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 new file mode 100644 index 00000000..e21bd584 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterAutoConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * 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.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.boot.GraphQlProperties; +import org.springframework.graphql.test.tester.GraphQlTester; +import org.springframework.graphql.web.WebGraphQlHandler; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +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 shouldNotBeConfiguredWithoutGraphQlHandlerNorWebTestClient() { + this.contextRunner.run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).doesNotHaveBean(GraphQlTester.class); + }); + } + + @Test + void shouldBeConfiguredWhenGraphQlHandlerPresent() { + this.contextRunner.withUserConfiguration(HandlerConfiguration.class).run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(GraphQlTester.class); + }); + } + + @Test + void shouldBeConfiguredWhenWebTestClientPresent() { + this.contextRunner.withUserConfiguration(WebTestClientConfiguration.class).run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(GraphQlTester.class); + }); + } + + @Configuration(proxyBeanMethods = false) + static class HandlerConfiguration { + + @Bean + WebGraphQlHandler webGraphQlHandler() { + return mock(WebGraphQlHandler.class); + } + + + } + + @Configuration(proxyBeanMethods = false) + static class WebTestClientConfiguration { + + @Bean + WebTestClient webTestClient() { + RouterFunction routerFunction = RouterFunctions.route() + .POST("/graphql", request -> ServerResponse.ok().build()).build(); + return WebTestClient.bindToRouterFunction(routerFunction).build(); + } + + @Bean + GraphQlProperties graphQlProperties() { + return new GraphQlProperties(); + } + + } +} diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerIntegrationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerIntegrationTests.java new file mode 100644 index 00000000..b2df7a37 --- /dev/null +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/test/tester/GraphQlTesterContextCustomizerIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * 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 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.boot.GraphQlProperties; +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 test() { + this.graphQlTester.query("{}").executeAndVerify(); + } + + @Configuration(proxyBeanMethods = false) + static class TestConfig { + + @Bean + TomcatReactiveWebServerFactory webServerFactory() { + return new TomcatReactiveWebServerFactory(0); + } + + @Bean + HttpHandler httpHandler() { + TestHandler httpHandler = new TestHandler(); + GraphQlProperties properties = new GraphQlProperties(); + Map handlersMap = Collections.singletonMap(properties.getPath(), 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()))); + } + + } +} \ No newline at end of file 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 8893d6a4..f2a5cf1e 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 @@ -15,36 +15,28 @@ */ package io.spring.sample.graphql.project; -import org.junit.jupiter.api.BeforeEach; 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.test.tester.GraphQlTester; -import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.client.MockMvcWebTestClient; import static org.assertj.core.api.Assertions.assertThat; /** - * GraphQL requests via {@link WebTestClient} connecting to {@link MockMvc}. + * GraphQL requests via {@link GraphQlTester} connecting to {@link MockMvc}. */ @SpringBootTest @AutoConfigureMockMvc +@AutoConfigureGraphQlTester public class MockMvcGraphQlTests { + @Autowired private GraphQlTester graphQlTester; - - @BeforeEach - public void setUp(@Autowired MockMvc mockMvc) { - WebTestClient client = MockMvcWebTestClient.bindTo(mockMvc).baseUrl("/graphql").build(); - this.graphQlTester = GraphQlTester.create(client); - } - - @Test void jsonPath() { String query = "{" +