diff --git a/spring-boot-project/spring-boot-parent/src/checkstyle/checkstyle.xml b/spring-boot-project/spring-boot-parent/src/checkstyle/checkstyle.xml index fc343f4c1640..7eb65e709327 100644 --- a/spring-boot-project/spring-boot-parent/src/checkstyle/checkstyle.xml +++ b/spring-boot-project/spring-boot-parent/src/checkstyle/checkstyle.xml @@ -70,7 +70,7 @@ + value="io.restassured.RestAssured.*, org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.springframework.boot.test.autoconfigure.AutoConfigurationImportedCondition.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.ArgumentMatchers.*, org.mockito.Matchers.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.restassured3.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo, org.springframework.test.web.client.ExpectedCount.*, org.springframework.test.web.client.match.MockRestRequestMatchers.*, org.springframework.test.web.client.response.MockRestResponseCreators.*" /> diff --git a/spring-boot-project/spring-boot-test-autoconfigure/pom.xml b/spring-boot-project/spring-boot-test-autoconfigure/pom.xml index 00c9a685ae14..f435b8ec187b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-test-autoconfigure/pom.xml @@ -151,6 +151,11 @@ spring-restdocs-restassured true + + org.springframework.restdocs + spring-restdocs-webtestclient + true + org.springframework.security spring-security-config diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java index 7941b6cb1e7b..be6c39cf7b02 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java @@ -35,12 +35,15 @@ import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.restassured3.RestAssuredRestDocumentation; import org.springframework.restdocs.restassured3.RestAssuredRestDocumentationConfigurer; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring REST Docs. * * @author Andy Wilkinson * @author Eddú Meléndez + * @author Roman Zaynetdinov * @since 1.4.0 */ @Configuration @@ -108,4 +111,33 @@ public RestDocsRestAssuredBuilderCustomizer restAssuredBuilderCustomizer( } + @Configuration + @ConditionalOnClass(WebTestClientRestDocumentation.class) + @ConditionalOnWebApplication(type = Type.REACTIVE) + static class RestDocsWebTestClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(WebTestClientRestDocumentationConfigurer.class) + public WebTestClientRestDocumentationConfigurer restDocsWebTestClientConfigurer( + ObjectProvider configurationCustomizerProvider, + RestDocumentationContextProvider contextProvider) { + WebTestClientRestDocumentationConfigurer configurer = WebTestClientRestDocumentation + .documentationConfiguration(contextProvider); + RestDocsWebTestClientConfigurationCustomizer configurationCustomizer = configurationCustomizerProvider + .getIfAvailable(); + if (configurationCustomizer != null) { + configurationCustomizer.customize(configurer); + } + return configurer; + } + + @Bean + @ConfigurationProperties(prefix = "spring.test.restdocs") + public RestDocsWebTestClientBuilderCustomizer restDocumentationConfigurer( + WebTestClientRestDocumentationConfigurer configurer) { + return new RestDocsWebTestClientBuilderCustomizer(configurer); + } + + } + } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientBuilderCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientBuilderCustomizer.java new file mode 100644 index 000000000000..22e72b342b7e --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientBuilderCustomizer.java @@ -0,0 +1,90 @@ +/* + * 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.autoconfigure.restdocs; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientBuilderCustomizer; +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.StringUtils; + +/** + * A customizer that configures Spring REST Docs with WebTestClient. + * + * @author Eddú Meléndez + * @author Roman Zaynetdinov + */ +class RestDocsWebTestClientBuilderCustomizer implements InitializingBean, WebTestClientBuilderCustomizer { + + private final WebTestClientRestDocumentationConfigurer delegate; + + private String uriScheme; + + private String uriHost; + + private Integer uriPort; + + RestDocsWebTestClientBuilderCustomizer(WebTestClientRestDocumentationConfigurer delegate) { + this.delegate = delegate; + } + + public String getUriScheme() { + return this.uriScheme; + } + + public void setUriScheme(String uriScheme) { + this.uriScheme = uriScheme; + } + + public String getUriHost() { + return this.uriHost; + } + + public void setUriHost(String uriHost) { + this.uriHost = uriHost; + } + + public Integer getUriPort() { + return this.uriPort; + } + + public void setUriPort(Integer uriPort) { + this.uriPort = uriPort; + } + + @Override + public void afterPropertiesSet() throws Exception { + } + + @Override + public void customize(WebTestClient.Builder builder) { + if (StringUtils.hasText(this.uriScheme) && StringUtils.hasText(this.uriHost)) { + String baseUrl = this.uriScheme + "://" + this.uriHost; + + if (this.uriPort == 80 && this.uriScheme.equals("http")) { + // Don't add default port + } else if (this.uriPort == 443 && this.uriScheme.equals("https")) { + // Don't add default port + } else if (this.uriPort != null) { + baseUrl += ":" + this.uriPort; + } + + builder.baseUrl(baseUrl); + } + builder.filter(this.delegate); + } +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientConfigurationCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientConfigurationCustomizer.java new file mode 100644 index 000000000000..cebff084c7c3 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsWebTestClientConfigurationCustomizer.java @@ -0,0 +1,42 @@ +/* + * 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.autoconfigure.restdocs; + +import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer; + +/** + * A customizer for {@link WebTestClientRestDocumentationConfigurer}. If a + * {@code RestDocsWebTestClientConfigurationCustomizer} bean is found in the application context + * it will be {@link #customize called} to customize the + * {@code WebTestClientRestDocumentationConfigurer} before it is applied. Intended for use only + * when the attributes on {@link AutoConfigureRestDocs} do not provide sufficient + * customization. + * + * @author Andy Wilkinson + * @author Roman Zaynetdinov + * @since 2.0.0 + */ +@FunctionalInterface +public interface RestDocsWebTestClientConfigurationCustomizer { + + /** + * Customize the given {@code configurer}. + * @param configurer the configurer + */ + void customize(WebTestClientRestDocumentationConfigurer configurer); + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/SpringBootWebTestClientBuilderCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/SpringBootWebTestClientBuilderCustomizer.java new file mode 100644 index 000000000000..6b6c2ce90208 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/SpringBootWebTestClientBuilderCustomizer.java @@ -0,0 +1,82 @@ +/* + * 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.autoconfigure.web.reactive; + +import java.time.Duration; +import java.util.Collection; +import java.util.function.Consumer; + +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.web.codec.CodecCustomizer; +import org.springframework.context.ApplicationContext; +import org.springframework.http.codec.ClientCodecConfigurer; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.web.reactive.function.client.ExchangeStrategies; + +/** + * {@link WebTestClientBuilderCustomizer} for a typical Spring Boot application. Usually applied + * automatically via {@link AutoConfigureWebTestClient @AutoConfigureWebTestClient}, but may also be + * used directly. + * + * @author Phillip Webb + * @author Andy Wilkinson + * @author Roman Zaynetdinov + * @since 2.0.0 + */ +public class SpringBootWebTestClientBuilderCustomizer implements WebTestClientBuilderCustomizer { + + private final ApplicationContext context; + + /** + * Create a new {@link SpringBootWebTestClientBuilderCustomizer} instance. + * @param context the source application context + */ + public SpringBootWebTestClientBuilderCustomizer(ApplicationContext context) { + Assert.notNull(context, "Context must not be null"); + this.context = context; + } + + @Override + public void customize(WebTestClient.Builder builder) { + customizeWebTestClient(builder); + customizeWebTestClientCodecs(builder); + } + + private void customizeWebTestClient(WebTestClient.Builder builder) { + Binder.get(this.context.getEnvironment()) + .bind("spring.test.webtestclient.timeout", Duration.class) + .ifBound(builder::responseTimeout); + } + + private void customizeWebTestClientCodecs(WebTestClient.Builder builder) { + Collection customizers = this.context + .getBeansOfType(CodecCustomizer.class).values(); + if (!CollectionUtils.isEmpty(customizers)) { + builder.exchangeStrategies(ExchangeStrategies.builder() + .codecs(this.applyCustomizers(customizers)).build()); + } + } + + private Consumer applyCustomizers( + Collection customizers) { + return (codecs) -> customizers + .forEach((customizer) -> customizer.customize(codecs)); + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientAutoConfiguration.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientAutoConfiguration.java index 8997ebb4bdd6..817d5651fa1e 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientAutoConfiguration.java @@ -16,30 +16,23 @@ package org.springframework.boot.test.autoconfigure.web.reactive; -import java.time.Duration; -import java.util.Collection; -import java.util.function.Consumer; +import java.util.List; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.reactive.server.WebTestClient.Builder; -import org.springframework.util.CollectionUtils; -import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; /** * Auto-configuration for {@link WebTestClient}. * * @author Stephane Nicoll + * @author Roman Zaynetdinov * @since 2.0.0 */ @Configuration @@ -47,37 +40,26 @@ @AutoConfigureAfter(CodecsAutoConfiguration.class) public class WebTestClientAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public WebTestClient webTestClient(ApplicationContext applicationContext) { - WebTestClient.Builder builder = WebTestClient - .bindToApplicationContext(applicationContext).configureClient(); - customizeWebTestClient(builder, applicationContext); - customizeWebTestClientCodecs(builder, applicationContext); - return builder.build(); - } + private final ApplicationContext context; - private void customizeWebTestClient(Builder builder, - ApplicationContext applicationContext) { - Binder.get(applicationContext.getEnvironment()) - .bind("spring.test.webtestclient.timeout", Duration.class) - .ifBound(builder::responseTimeout); + WebTestClientAutoConfiguration(ApplicationContext context) { + this.context = context; } - private void customizeWebTestClientCodecs(WebTestClient.Builder builder, - ApplicationContext applicationContext) { - Collection customizers = applicationContext - .getBeansOfType(CodecCustomizer.class).values(); - if (!CollectionUtils.isEmpty(customizers)) { - builder.exchangeStrategies(ExchangeStrategies.builder() - .codecs(applyCustomizers(customizers)).build()); + @Bean + @ConditionalOnMissingBean + public WebTestClient webTestClient(List customizers) { + WebTestClient.Builder builder = WebTestClient + .bindToApplicationContext(this.context).configureClient(); + for (WebTestClientBuilderCustomizer customizer : customizers) { + customizer.customize(builder); } + return builder.build(); } - private Consumer applyCustomizers( - Collection customizers) { - return (codecs) -> customizers - .forEach((customizer) -> customizer.customize(codecs)); + @Bean + public SpringBootWebTestClientBuilderCustomizer springBootWebTestClientBuilderCustomizer() { + return new SpringBootWebTestClientBuilderCustomizer(this.context); } } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientBuilderCustomizer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientBuilderCustomizer.java new file mode 100644 index 000000000000..39ea6b189d64 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientBuilderCustomizer.java @@ -0,0 +1,40 @@ +/* + * 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.autoconfigure.web.reactive; + +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * A customizer for a {@link WebTestClient.Builder}. Any + * {@code WebTestClientBuilderCustomizer} beans found in the application context will be + * {@link #customize called} to customize the auto-configured {@link WebTestClient.Builder}. + * + * @author Andy Wilkinson + * @author Roman Zaynetdinov + * @since 2.0.0 + * @see WebTestClientAutoConfiguration + */ +@FunctionalInterface +public interface WebTestClientBuilderCustomizer { + + /** + * Customize the given {@code builder}. + * @param builder the builder + */ + void customize(WebTestClient.Builder builder); + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationIntegrationTests.java new file mode 100644 index 000000000000..d0558fe566c9 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/WebTestClientRestDocsAutoConfigurationIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * 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.autoconfigure.restdocs; + +import java.io.File; + +import org.assertj.core.api.Condition; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.FileSystemUtils; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; + +/** + * Integration tests for {@link RestDocsAutoConfiguration} with WebClientTest. + * + * @author Andy Wilkinson + * @author Roman Zaynetdinov + */ +@RunWith(SpringRunner.class) +@WebFluxTest +@AutoConfigureRestDocs(uriScheme = "https", uriHost = "api.example.com", uriPort = 443) +public class WebTestClientRestDocsAutoConfigurationIntegrationTests { + + @Before + public void deleteSnippets() { + FileSystemUtils.deleteRecursively(new File("target/generated-snippets")); + } + + @Autowired + private WebTestClient webTestClient; + + @Test + public void defaultSnippetsAreWritten() throws Exception { + this.webTestClient.get().uri("/").exchange() + .expectBody().consumeWith(document("default-snippets")); + File defaultSnippetsDir = new File("target/generated-snippets/default-snippets"); + assertThat(defaultSnippetsDir).exists(); + assertThat(new File(defaultSnippetsDir, "curl-request.adoc")) + .has(contentContaining("'https://api.example.com/'")); + assertThat(new File(defaultSnippetsDir, "http-request.adoc")) + .has(contentContaining("api.example.com")); + assertThat(new File(defaultSnippetsDir, "http-response.adoc")).isFile(); + } + + private Condition contentContaining(String toContain) { + return new ContentContainingCondition(toContain); + } + +}