diff --git a/spring-boot-samples/spring-boot-sample-webflux/src/test/java/sample/webflux/SampleWebFluxApplicationIntegrationTests.java b/spring-boot-samples/spring-boot-sample-webflux/src/test/java/sample/webflux/SampleWebFluxApplicationIntegrationTests.java index 66ced9899e0..9ea98e74415 100644 --- a/spring-boot-samples/spring-boot-sample-webflux/src/test/java/sample/webflux/SampleWebFluxApplicationIntegrationTests.java +++ b/spring-boot-samples/spring-boot-sample-webflux/src/test/java/sample/webflux/SampleWebFluxApplicationIntegrationTests.java @@ -16,18 +16,15 @@ package sample.webflux; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; -import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.test.web.reactive.server.WebTestClient; /** * Basic integration tests for WebFlux application. @@ -38,27 +35,16 @@ import org.springframework.web.reactive.function.client.WebClient; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class SampleWebFluxApplicationIntegrationTests { - @LocalServerPort - private int port; - - private WebClient webClient; - - @Before - public void setUp() throws Exception { - this.webClient = WebClient.create("http://localhost:" + this.port); - } + @Autowired + private WebTestClient webClient; @Test public void testWelcome() throws Exception { - Mono body = this.webClient + this.webClient .get().uri("/") .accept(MediaType.TEXT_PLAIN) .exchange() - .then(response -> response.bodyToMono(String.class)); - - StepVerifier.create(body) - .expectNext("Hello World") - .verifyComplete(); + .expectBody(String.class).value().isEqualTo("Hello World"); } } diff --git a/spring-boot-test/pom.xml b/spring-boot-test/pom.xml index 72850b1c04e..47a63139960 100644 --- a/spring-boot-test/pom.xml +++ b/spring-boot-test/pom.xml @@ -40,6 +40,11 @@ json-path true + + io.projectreactor.ipc + reactor-netty + true + javax.servlet javax.servlet-api diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java new file mode 100644 index 00000000000..f3bede933da --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizer.java @@ -0,0 +1,134 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.context.embedded.AbstractConfigurableReactiveWebServer; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * {@link ContextCustomizer} for {@link WebTestClient}. + * + * @author Stephane Nicoll + */ +class WebTestClientContextCustomizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, + MergedContextConfiguration mergedConfig) { + SpringBootTest annotation = AnnotatedElementUtils.getMergedAnnotation( + mergedConfig.getTestClass(), SpringBootTest.class); + if (annotation.webEnvironment().isEmbedded()) { + registerWebTestClient(context); + } + } + + private void registerWebTestClient(ConfigurableApplicationContext context) { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + if (beanFactory instanceof BeanDefinitionRegistry) { + registerWebTestClient(context, (BeanDefinitionRegistry) context); + } + + } + + private void registerWebTestClient(ConfigurableApplicationContext context, + BeanDefinitionRegistry registry) { + registry.registerBeanDefinition(WebTestClient.class.getName(), + new RootBeanDefinition(WebTestClientFactory.class)); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + return true; + } + + /** + * {@link FactoryBean} used to create and configure a {@link WebTestClient}. + */ + public static class WebTestClientFactory + implements FactoryBean, ApplicationContextAware { + + private ApplicationContext applicationContext; + + private WebTestClient object; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public Class getObjectType() { + return WebTestClient.class; + } + + @Override + public WebTestClient getObject() throws Exception { + if (this.object == null) { + this.object = createWebTestClient(); + } + return this.object; + } + + private WebTestClient createWebTestClient() { + boolean sslEnabled = isSslEnabled(this.applicationContext); + String port = this.applicationContext.getEnvironment() + .getProperty("local.server.port", "8080"); + String baseUrl = (sslEnabled ? "https" : "http") + "://localhost:" + port; + return WebTestClient.bindToServer().baseUrl(baseUrl).build(); + } + + private boolean isSslEnabled(ApplicationContext context) { + try { + AbstractConfigurableReactiveWebServer container = context + .getBean(AbstractConfigurableReactiveWebServer.class); + return container.getSsl() != null && container.getSsl().isEnabled(); + } + catch (NoSuchBeanDefinitionException ex) { + return false; + } + } + + } + +} diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java b/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java new file mode 100644 index 00000000000..6c3d8ed0fee --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/WebTestClientContextCustomizerFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.web.reactive; + +import java.util.List; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.util.ClassUtils; + +/** + * {@link ContextCustomizerFactory} for {@code WebTestClient}. + * + * @author Stephane Nicoll + */ +public class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory { + + private static final String WEB_TEST_CLIENT_CLASS = + "org.springframework.web.reactive.function.client.WebClient"; + + @Override + public ContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + if (isWebClientPresent() && AnnotatedElementUtils.findMergedAnnotation(testClass, + SpringBootTest.class) != null) { + return new WebTestClientContextCustomizer(); + } + return null; + } + + private boolean isWebClientPresent() { + return ClassUtils.isPresent(WEB_TEST_CLIENT_CLASS, getClass().getClassLoader()); + } + +} diff --git a/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-test/src/main/resources/META-INF/spring.factories index 10c125a43ea..3a9340c3bc8 100644 --- a/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -3,7 +3,8 @@ org.springframework.test.context.ContextCustomizerFactory=\ org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ org.springframework.boot.test.context.SpringBootTestContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ -org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory +org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\ +org.springframework.boot.test.web.reactive.WebTestClientContextCustomizerFactory # Test Execution Listeners org.springframework.test.context.TestExecutionListener=\ diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/context/AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/context/AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests.java index 8051efbd9dc..5ec524e376a 100644 --- a/spring-boot-test/src/test/java/org/springframework/boot/test/context/AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests.java +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/context/AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests.java @@ -29,8 +29,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.client.RestTemplate; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -52,6 +52,9 @@ public abstract class AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests @Autowired private ReactiveWebApplicationContext context; + @Autowired + private WebTestClient webClient; + public ReactiveWebApplicationContext getContext() { return this.context; } @@ -59,9 +62,19 @@ public abstract class AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests @Test public void runAndTestHttpEndpoint() { assertThat(this.port).isNotEqualTo(8080).isNotEqualTo(0); - String body = new RestTemplate() - .getForObject("http://localhost:" + this.port + "/", String.class); - assertThat(body).isEqualTo("Hello World"); + WebTestClient.bindToServer() + .baseUrl("http://localhost:" + this.port).build() + .get().uri("/") + .exchange() + .expectBody(String.class).value().isEqualTo("Hello World"); + } + + @Test + public void injectWebTestClient() { + this.webClient + .get().uri("/") + .exchange() + .expectBody(String.class).value().isEqualTo("Hello World"); } @Test