diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index 12ffdb60994..865e9bb6dc5 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -128,6 +128,7 @@ dependencies { implementation(project(path: ":module:spring-boot-security")) implementation(project(path: ":module:spring-boot-tomcat")) implementation(project(path: ":module:spring-boot-webclient")) + implementation(project(path: ":module:spring-boot-webclient-test")) implementation(project(path: ":module:spring-boot-webflux")) implementation(project(path: ":module:spring-boot-webflux-test")) implementation(project(path: ":module:spring-boot-webmvc")) diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc index d73e3a1fc81..1bb88975e7a 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc @@ -729,6 +729,20 @@ include-code::MyRestClientServiceTests[] +[[testing.spring-boot-applications.autoconfigured-web-client]] +== Auto-configured Web Clients + +You can use the javadoc:org.springframework.boot.webclient.test.autoconfigure.WebClientTest[format=annotation] annotation from the `spring-boot-webclient-test` module to test code that uses `WebClient`. +By default, it auto-configures Jackson, GSON, and Jsonb support, and configures a javadoc:org.springframework.web.reactive.function.client.WebClient$Builder[]. +Regular javadoc:org.springframework.stereotype.Component[format=annotation] and javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans are not scanned when the javadoc:org.springframework.boot.webclient.test.autoconfigure.WebClientTest[format=annotation] annotation is used. +javadoc:org.springframework.boot.context.properties.EnableConfigurationProperties[format=annotation] can be used to include javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans. + +TIP: A list of the auto-configuration settings that are enabled by javadoc:org.springframework.boot.webclient.test.autoconfigure.WebClientTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix]. + +The specific beans that you want to test should be specified by using the `value` or `components` attribute of javadoc:org.springframework.boot.webclient.test.autoconfigure.WebClientTest[format=annotation]. + + + [[testing.spring-boot-applications.autoconfigured-spring-restdocs]] == Auto-configured Spring REST Docs Tests diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc index f1664260821..6ff48d410e4 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc @@ -64,6 +64,9 @@ Spring Boot offers several focused, feature-specific `-test` modules: |`spring-boot-security-test` |Testing applications that use Spring Security. +|`spring-boot-webclient-test` +|Testing applications that use `WebClient`. Provides the `@WebClientTest` test slice. + |`spring-boot-webflux-test` |Testing applications that use Spring WebFlux. Provides the `@WebFluxTest` test slice. diff --git a/module/spring-boot-webclient-test/build.gradle b/module/spring-boot-webclient-test/build.gradle new file mode 100644 index 00000000000..00a7b00d94a --- /dev/null +++ b/module/spring-boot-webclient-test/build.gradle @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present 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. + */ + +plugins { + id "java-library" + id "org.springframework.boot.deployed" + id "org.springframework.boot.optional-dependencies" + id "org.springframework.boot.test-slice" +} + +description = "Spring Boot WebClient Test" + +dependencies { + api(project(":core:spring-boot-test-autoconfigure")) + api(project(":module:spring-boot-webclient")) + + optional(project(":core:spring-boot-autoconfigure")) + optional(project(":module:spring-boot-jackson")) + optional("org.apache.httpcomponents.client5:httpclient5") + optional("org.junit.jupiter:junit-jupiter-api") + optional("org.springframework:spring-test") + + testImplementation(project(":core:spring-boot-test")) + testImplementation(project(":module:spring-boot-micrometer-metrics")) + testImplementation(project(":test-support:spring-boot-test-support")) + testImplementation("com.squareup.okhttp3:mockwebserver") + + testRuntimeOnly("ch.qos.logback:logback-classic") +} diff --git a/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/AutoConfigureWebClient.java b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/AutoConfigureWebClient.java new file mode 100644 index 00000000000..44fcfcd8e88 --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/AutoConfigureWebClient.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +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.json.AutoConfigureJson; +import org.springframework.web.client.RestClient.Builder; + +/** + * Annotation that can be applied to a test class to enable auto-configuration of a + * {@link Builder WebClient.Builder}. + * + * @author Andy Wilkinson + * @since 4.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@AutoConfigureJson +@ImportAutoConfiguration +public @interface AutoConfigureWebClient { + +} diff --git a/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTest.java b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTest.java new file mode 100644 index 00000000000..04eedadbc7d --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.context.filter.annotation.TypeExcludeFilters; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.env.Environment; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.RestClient.Builder; + +/** + * Annotation for a Spring WebClient test that focuses only on beans that + * use {@link Builder WebClient.Builder}. + *

+ * Using this annotation only enables auto-configuration that is relevant to rest client + * tests. Similarly, component scanning is limited to beans annotated with: + *

+ *

+ * as well as beans that implement: + *

+ *

+ * When using JUnit 4, this annotation should be used in combination with + * {@code @RunWith(SpringRunner.class)}. + * + * @author Andy Wilkinson + * @since 4.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@BootstrapWith(WebClientTestContextBootstrapper.class) +@ExtendWith(SpringExtension.class) +@OverrideAutoConfiguration(enabled = false) +@TypeExcludeFilters(WebClientTypeExcludeFilter.class) +@AutoConfigureWebClient +@ImportAutoConfiguration +public @interface WebClientTest { + + /** + * Properties in form {@literal key=value} that should be added to the Spring + * {@link Environment} before the test runs. + * @return the properties to add + */ + String[] properties() default {}; + + /** + * Specifies the components to test. This is an alias of {@link #components()} which + * can be used for brevity if no other attributes are defined. See + * {@link #components()} for details. + * @see #components() + * @return the components to test + */ + @AliasFor("components") + Class[] value() default {}; + + /** + * Specifies the components to test. May be left blank if components will be manually + * imported or created directly. + * @see #value() + * @return the components to test + */ + @AliasFor("value") + Class[] components() default {}; + + /** + * Determines if default filtering should be used with + * {@link SpringBootApplication @SpringBootApplication}. By default only + * {@code @JsonComponent} and {@code Module} beans are included. + * @see #includeFilters() + * @see #excludeFilters() + * @return if default filters should be used + */ + boolean useDefaultFilters() default true; + + /** + * A set of include filters which can be used to add otherwise filtered beans to the + * application context. + * @return include filters to apply + */ + ComponentScan.Filter[] includeFilters() default {}; + + /** + * A set of exclude filters which can be used to filter beans that would otherwise be + * added to the application context. + * @return exclude filters to apply + */ + ComponentScan.Filter[] excludeFilters() default {}; + + /** + * Auto-configuration exclusions that should be applied for this test. + * @return auto-configuration exclusions to apply + */ + @AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude") + Class[] excludeAutoConfiguration() default {}; + +} diff --git a/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestContextBootstrapper.java b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestContextBootstrapper.java new file mode 100644 index 00000000000..3582c92a7e7 --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestContextBootstrapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.springframework.boot.test.autoconfigure.TestSliceTestContextBootstrapper; +import org.springframework.test.context.TestContextBootstrapper; + +/** + * {@link TestContextBootstrapper} for {@link WebClientTest @WebClientTest} support. + * + * @author Andy Wilkinson + */ +class WebClientTestContextBootstrapper extends TestSliceTestContextBootstrapper { + +} diff --git a/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTypeExcludeFilter.java b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTypeExcludeFilter.java new file mode 100644 index 00000000000..9145738aefd --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTypeExcludeFilter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.context.TypeExcludeFilter; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter; +import org.springframework.util.ClassUtils; + +/** + * {@link TypeExcludeFilter} for {@link WebClientTest @WebClientTest}. + * + * @author Andy Wilkinson + */ +class WebClientTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter { + + private static final Class[] NO_COMPONENTS = {}; + + private static final String DATABIND_MODULE_CLASS_NAME = "tools.jackson.databind.JacksonModule"; + + private static final Set> KNOWN_INCLUDES; + + static { + Set> includes = new LinkedHashSet<>(); + if (ClassUtils.isPresent(DATABIND_MODULE_CLASS_NAME, WebClientTypeExcludeFilter.class.getClassLoader())) { + try { + includes.add(Class.forName(DATABIND_MODULE_CLASS_NAME, true, + WebClientTypeExcludeFilter.class.getClassLoader())); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Failed to load " + DATABIND_MODULE_CLASS_NAME, ex); + } + includes.add(JsonComponent.class); + } + KNOWN_INCLUDES = Collections.unmodifiableSet(includes); + } + + private final Class[] components; + + WebClientTypeExcludeFilter(Class testClass) { + super(testClass); + this.components = getAnnotation().getValue("components", Class[].class).orElse(NO_COMPONENTS); + } + + @Override + protected Set> getKnownIncludes() { + return KNOWN_INCLUDES; + } + + @Override + protected Set> getComponentIncludes() { + return new LinkedHashSet<>(Arrays.asList(this.components)); + } + +} diff --git a/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/package-info.java b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/package-info.java new file mode 100644 index 00000000000..475427f41a5 --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/java/org/springframework/boot/webclient/test/autoconfigure/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-present 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 web clients. + */ +@NullMarked +package org.springframework.boot.webclient.test.autoconfigure; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webclient-test/src/main/resources/META-INF/spring/org.springframework.boot.webclient.test.autoconfigure.AutoConfigureWebClient.imports b/module/spring-boot-webclient-test/src/main/resources/META-INF/spring/org.springframework.boot.webclient.test.autoconfigure.AutoConfigureWebClient.imports new file mode 100644 index 00000000000..7d602e14fd6 --- /dev/null +++ b/module/spring-boot-webclient-test/src/main/resources/META-INF/spring/org.springframework.boot.webclient.test.autoconfigure.AutoConfigureWebClient.imports @@ -0,0 +1,2 @@ +org.springframework.boot.http.codec.autoconfigure.CodecsAutoConfiguration +org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/AnotherExampleWebClientService.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/AnotherExampleWebClientService.java new file mode 100644 index 00000000000..07193bb3d18 --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/AnotherExampleWebClientService.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * A second example web client used with {@link WebClientTest @WebClientTest} tests. + * + * @author Scott Frederick + */ +@Service +public class AnotherExampleWebClientService { + + private final WebClient.Builder builder; + + private final WebClient webClient; + + public AnotherExampleWebClientService(WebClient.Builder builder) { + this.builder = builder; + this.webClient = builder.baseUrl("https://example.com").build(); + } + + protected WebClient.Builder geWebClientBuilder() { + return this.builder; + } + + public String test() { + return this.webClient.get().uri("/test").retrieve().toEntity(String.class).block().getBody(); + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleProperties.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleProperties.java new file mode 100644 index 00000000000..e51a5cd0ba3 --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; +import org.springframework.boot.context.properties.bind.DefaultValue; + +/** + * Example {@link ConstructorBinding constructor-bound} + * {@link ConfigurationProperties @ConfigurationProperties} used to test the use of + * configuration properties scan with sliced test. + * + * @author Stephane Nicoll + */ +@ConfigurationProperties("example") +public class ExampleProperties { + + private final String name; + + public ExampleProperties(@DefaultValue("test") String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientApplication.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientApplication.java new file mode 100644 index 00000000000..a65cd4c0006 --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +/** + * Example {@link SpringBootApplication @SpringBootApplication} used with + * {@link WebClientTest @WebClientTest} tests. + * + * @author Phillip Webb + */ +@SpringBootApplication +@ConfigurationPropertiesScan +public class ExampleWebClientApplication { + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientService.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientService.java new file mode 100644 index 00000000000..7a05cd769e8 --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/ExampleWebClientService.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Example web client using {@code WebClient} with {@link WebClientTest @WebClientTest} + * tests. + * + * @author Scott Frederick + */ +@Service +public class ExampleWebClientService { + + private final WebClient.Builder builder; + + private final WebClient webClient; + + public ExampleWebClientService(WebClient.Builder builder) { + this.builder = builder; + this.webClient = builder.build(); + } + + protected WebClient.Builder getWebClientBuilder() { + return this.builder; + } + + public String test() { + return this.webClient.get().uri("/test").retrieve().toEntity(String.class).block().getBody(); + } + + public void testPostWithBody(String body) { + this.webClient.post().uri("/test").bodyValue(body).retrieve().toBodilessEntity().block(); + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/MockWebServerConfiguration.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/MockWebServerConfiguration.java new file mode 100644 index 00000000000..cd6fc8ddbcf --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/MockWebServerConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import okhttp3.mockwebserver.MockWebServer; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.webclient.WebClientCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Configuration for a {@link MockWebServer}. + * + * @author Andy Wilkinson + */ +@Configuration(proxyBeanMethods = false) +public class MockWebServerConfiguration implements DisposableBean, WebClientCustomizer { + + private final MockWebServer mockWebServer = new MockWebServer(); + + MockWebServerConfiguration() { + try { + this.mockWebServer.start(); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public void destroy() { + try { + this.mockWebServer.shutdown(); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public void customize(WebClient.Builder webClientBuilder) { + webClientBuilder.baseUrl(this.mockWebServer.url("/").toString()); + } + + @Bean + MockWebServer mockWebServer() { + return this.mockWebServer; + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestIntegrationTests.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestIntegrationTests.java new file mode 100644 index 00000000000..9947012f84f --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import java.nio.charset.StandardCharsets; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebClientTest @WebClientTest}. + * + * @author Scott Frederick + */ +@WebClientTest(ExampleWebClientService.class) +@Import(MockWebServerConfiguration.class) +class WebClientTestIntegrationTests { + + @Autowired + private MockWebServer server; + + @Autowired + private ExampleWebClientService client; + + @Test + void mockServerCall1() throws InterruptedException { + this.server.enqueue(new MockResponse().setBody("1")); + assertThat(this.client.test()).isEqualTo("1"); + this.server.takeRequest(); + } + + @Test + void mockServerCall2() throws InterruptedException { + this.server.enqueue(new MockResponse().setBody("2")); + assertThat(this.client.test()).isEqualTo("2"); + this.server.takeRequest(); + } + + @Test + void mockServerCallWithContent() throws InterruptedException { + this.server.enqueue(new MockResponse().setBody("1")); + this.client.testPostWithBody("test"); + assertThat(this.server.takeRequest().getBody().readString(StandardCharsets.UTF_8)).isEqualTo("test"); + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestNoComponentIntegrationTests.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestNoComponentIntegrationTests.java new file mode 100644 index 00000000000..e243176677b --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestNoComponentIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link WebClientTest @WebClientTest} with no specific client. + * + * @author Phillip Webb + */ +@WebClientTest +@Import(MockWebServerConfiguration.class) +class WebClientTestNoComponentIntegrationTests { + + @Autowired + private MockWebServer server; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private WebClient.Builder webClientBuilder; + + @Test + void exampleWebClientServiceIsNotInjected() { + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> this.applicationContext.getBean(ExampleWebClientService.class)); + } + + @Test + void examplePropertiesIsNotInjected() { + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> this.applicationContext.getBean(ExampleProperties.class)); + } + + @Test + void manuallyCreateBean() throws InterruptedException { + ExampleWebClientService client = new ExampleWebClientService(this.webClientBuilder); + this.server.enqueue(new MockResponse().setBody("hello")); + assertThat(client.test()).isEqualTo("hello"); + this.server.takeRequest(); + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestPropertiesIntegrationTests.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestPropertiesIntegrationTests.java new file mode 100644 index 00000000000..1a942ad40cf --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestPropertiesIntegrationTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for the {@link WebClientTest#properties properties} attribute of + * {@link WebClientTest @WebClientTest}. + * + * @author Artsiom Yudovin + */ +@WebClientTest(properties = "spring.profiles.active=test") +class WebClientTestPropertiesIntegrationTests { + + @Autowired + private Environment environment; + + @Test + void environmentWithNewProfile() { + assertThat(this.environment.getActiveProfiles()).containsExactly("test"); + } + + @Nested + class NestedTests { + + @Autowired + private Environment innerEnvironment; + + @Test + void propertiesFromEnclosingClassAffectNestedTests() { + assertThat(WebClientTestPropertiesIntegrationTests.this.environment.getActiveProfiles()) + .containsExactly("test"); + assertThat(this.innerEnvironment.getActiveProfiles()).containsExactly("test"); + } + + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithConfigurationPropertiesIntegrationTests.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithConfigurationPropertiesIntegrationTests.java new file mode 100644 index 00000000000..9a5412fb1c0 --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithConfigurationPropertiesIntegrationTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebClientTest @WebClientTest} with a + * {@link ConfigurationProperties @ConfigurationProperties} annotated type. + * + * @author Stephane Nicoll + */ +@WebClientTest(components = ExampleProperties.class, properties = "example.name=Hello") +class WebClientTestWithConfigurationPropertiesIntegrationTests { + + @Autowired + private ApplicationContext applicationContext; + + @Test + void configurationPropertiesCanBeAddedAsComponent() { + assertThat(this.applicationContext.getBeansOfType(ExampleProperties.class).keySet()) + .containsOnly("example-" + ExampleProperties.class.getName()); + assertThat(this.applicationContext.getBean(ExampleProperties.class).getName()).isEqualTo("Hello"); + } + +} diff --git a/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithoutJacksonIntegrationTests.java b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithoutJacksonIntegrationTests.java new file mode 100644 index 00000000000..e126d62c41c --- /dev/null +++ b/module/spring-boot-webclient-test/src/test/java/org/springframework/boot/webclient/test/autoconfigure/WebClientTestWithoutJacksonIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-present 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.webclient.test.autoconfigure; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.context.annotation.Import; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link WebClientTest @WebClientTest} without Jackson. + * + * @author Andy Wilkinson + */ +@ClassPathExclusions("jackson-*.jar") +@WebClientTest(ExampleWebClientService.class) +@Import(MockWebServerConfiguration.class) +class WebClientTestWithoutJacksonIntegrationTests { + + @Autowired + private MockWebServer server; + + @Autowired + private ExampleWebClientService client; + + @Test + void webClientTestCanBeUsedWhenJacksonIsNotOnTheClassPath() throws InterruptedException { + ClassLoader classLoader = getClass().getClassLoader(); + assertThat(ClassUtils.isPresent("com.fasterxml.jackson.databind.Module", classLoader)).isFalse(); + assertThat(ClassUtils.isPresent("tools.jackson.databind.JacksonModule", classLoader)).isFalse(); + this.server.enqueue(new MockResponse().setBody("hello")); + assertThat(this.client.test()).isEqualTo("hello"); + this.server.takeRequest(); + } + +} diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 07613c3df6e..a48f549a56f 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2260,6 +2260,7 @@ bom { "spring-boot-validation", "spring-boot-web-server", "spring-boot-webclient", + "spring-boot-webclient-test", "spring-boot-webflux", "spring-boot-webflux-test", "spring-boot-webmvc", diff --git a/settings.gradle b/settings.gradle index 5c6ce4f3cfc..88ca33ee69a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -189,6 +189,7 @@ include "module:spring-boot-tx" include "module:spring-boot-validation" include "module:spring-boot-web-server" include "module:spring-boot-webclient" +include "module:spring-boot-webclient-test" include "module:spring-boot-webflux" include "module:spring-boot-webflux-test" include "module:spring-boot-webmvc" diff --git a/starter/spring-boot-starter-webclient-test/build.gradle b/starter/spring-boot-starter-webclient-test/build.gradle index 9563edcc913..35c9ccf6023 100644 --- a/starter/spring-boot-starter-webclient-test/build.gradle +++ b/starter/spring-boot-starter-webclient-test/build.gradle @@ -24,4 +24,6 @@ dependencies { api(project(":starter:spring-boot-starter-jackson-test")) api(project(":starter:spring-boot-starter-test")) api(project(":starter:spring-boot-starter-webclient")) + + api(project(":module:spring-boot-webclient-test")) }