Skip to content

Commit 885f90d

Browse files
committed
Introduce spring-boot-webclient-test
See gh-46356 See gh-47322
1 parent 1706a7c commit 885f90d

23 files changed

Lines changed: 906 additions & 0 deletions

documentation/spring-boot-docs/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ dependencies {
128128
implementation(project(path: ":module:spring-boot-security"))
129129
implementation(project(path: ":module:spring-boot-tomcat"))
130130
implementation(project(path: ":module:spring-boot-webclient"))
131+
implementation(project(path: ":module:spring-boot-webclient-test"))
131132
implementation(project(path: ":module:spring-boot-webflux"))
132133
implementation(project(path: ":module:spring-boot-webflux-test"))
133134
implementation(project(path: ":module:spring-boot-webmvc"))

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,20 @@ include-code::MyRestClientServiceTests[]
729729

730730

731731

732+
[[testing.spring-boot-applications.autoconfigured-web-client]]
733+
== Auto-configured Web Clients
734+
735+
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`.
736+
By default, it auto-configures Jackson, GSON, and Jsonb support, and configures a javadoc:org.springframework.web.reactive.function.client.WebClient$Builder[].
737+
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.
738+
javadoc:org.springframework.boot.context.properties.EnableConfigurationProperties[format=annotation] can be used to include javadoc:org.springframework.boot.context.properties.ConfigurationProperties[format=annotation] beans.
739+
740+
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].
741+
742+
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].
743+
744+
745+
732746
[[testing.spring-boot-applications.autoconfigured-spring-restdocs]]
733747
== Auto-configured Spring REST Docs Tests
734748

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ Spring Boot offers several focused, feature-specific `-test` modules:
6464
|`spring-boot-security-test`
6565
|Testing applications that use Spring Security.
6666

67+
|`spring-boot-webclient-test`
68+
|Testing applications that use `WebClient`. Provides the `@WebClientTest` test slice.
69+
6770
|`spring-boot-webflux-test`
6871
|Testing applications that use Spring WebFlux. Provides the `@WebFluxTest` test slice.
6972

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the License);
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
id "java-library"
19+
id "org.springframework.boot.deployed"
20+
id "org.springframework.boot.optional-dependencies"
21+
id "org.springframework.boot.test-slice"
22+
}
23+
24+
description = "Spring Boot WebClient Test"
25+
26+
dependencies {
27+
api(project(":core:spring-boot-test-autoconfigure"))
28+
api(project(":module:spring-boot-webclient"))
29+
30+
optional(project(":core:spring-boot-autoconfigure"))
31+
optional(project(":module:spring-boot-jackson"))
32+
optional("org.apache.httpcomponents.client5:httpclient5")
33+
optional("org.junit.jupiter:junit-jupiter-api")
34+
optional("org.springframework:spring-test")
35+
36+
testImplementation(project(":core:spring-boot-test"))
37+
testImplementation(project(":module:spring-boot-micrometer-metrics"))
38+
testImplementation(project(":test-support:spring-boot-test-support"))
39+
testImplementation("com.squareup.okhttp3:mockwebserver")
40+
41+
testRuntimeOnly("ch.qos.logback:logback-classic")
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webclient.test.autoconfigure;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
27+
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson;
28+
import org.springframework.web.client.RestClient.Builder;
29+
30+
/**
31+
* Annotation that can be applied to a test class to enable auto-configuration of a
32+
* {@link Builder WebClient.Builder}.
33+
*
34+
* @author Andy Wilkinson
35+
* @since 4.0.0
36+
*/
37+
@Target(ElementType.TYPE)
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Documented
40+
@Inherited
41+
@AutoConfigureJson
42+
@ImportAutoConfiguration
43+
public @interface AutoConfigureWebClient {
44+
45+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webclient.test.autoconfigure;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
import org.junit.jupiter.api.extension.ExtendWith;
27+
28+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.SpringBootApplication;
30+
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
31+
import org.springframework.boot.test.context.filter.annotation.TypeExcludeFilters;
32+
import org.springframework.context.annotation.ComponentScan;
33+
import org.springframework.core.annotation.AliasFor;
34+
import org.springframework.core.env.Environment;
35+
import org.springframework.test.context.BootstrapWith;
36+
import org.springframework.test.context.junit.jupiter.SpringExtension;
37+
import org.springframework.web.client.RestClient.Builder;
38+
39+
/**
40+
* Annotation for a Spring WebClient test that focuses <strong>only</strong> on beans that
41+
* use {@link Builder WebClient.Builder}.
42+
* <p>
43+
* Using this annotation only enables auto-configuration that is relevant to rest client
44+
* tests. Similarly, component scanning is limited to beans annotated with:
45+
* <ul>
46+
* <li>{@code @JsonComponent}</li>
47+
* </ul>
48+
* <p>
49+
* as well as beans that implement:
50+
* <ul>
51+
* <li>{@code JacksonModule}, if Jackson is available</li>
52+
* </ul>
53+
* <p>
54+
* When using JUnit 4, this annotation should be used in combination with
55+
* {@code @RunWith(SpringRunner.class)}.
56+
*
57+
* @author Andy Wilkinson
58+
* @since 4.0.0
59+
*/
60+
@Target(ElementType.TYPE)
61+
@Retention(RetentionPolicy.RUNTIME)
62+
@Documented
63+
@Inherited
64+
@BootstrapWith(WebClientTestContextBootstrapper.class)
65+
@ExtendWith(SpringExtension.class)
66+
@OverrideAutoConfiguration(enabled = false)
67+
@TypeExcludeFilters(WebClientTypeExcludeFilter.class)
68+
@AutoConfigureWebClient
69+
@ImportAutoConfiguration
70+
public @interface WebClientTest {
71+
72+
/**
73+
* Properties in form {@literal key=value} that should be added to the Spring
74+
* {@link Environment} before the test runs.
75+
* @return the properties to add
76+
*/
77+
String[] properties() default {};
78+
79+
/**
80+
* Specifies the components to test. This is an alias of {@link #components()} which
81+
* can be used for brevity if no other attributes are defined. See
82+
* {@link #components()} for details.
83+
* @see #components()
84+
* @return the components to test
85+
*/
86+
@AliasFor("components")
87+
Class<?>[] value() default {};
88+
89+
/**
90+
* Specifies the components to test. May be left blank if components will be manually
91+
* imported or created directly.
92+
* @see #value()
93+
* @return the components to test
94+
*/
95+
@AliasFor("value")
96+
Class<?>[] components() default {};
97+
98+
/**
99+
* Determines if default filtering should be used with
100+
* {@link SpringBootApplication @SpringBootApplication}. By default only
101+
* {@code @JsonComponent} and {@code Module} beans are included.
102+
* @see #includeFilters()
103+
* @see #excludeFilters()
104+
* @return if default filters should be used
105+
*/
106+
boolean useDefaultFilters() default true;
107+
108+
/**
109+
* A set of include filters which can be used to add otherwise filtered beans to the
110+
* application context.
111+
* @return include filters to apply
112+
*/
113+
ComponentScan.Filter[] includeFilters() default {};
114+
115+
/**
116+
* A set of exclude filters which can be used to filter beans that would otherwise be
117+
* added to the application context.
118+
* @return exclude filters to apply
119+
*/
120+
ComponentScan.Filter[] excludeFilters() default {};
121+
122+
/**
123+
* Auto-configuration exclusions that should be applied for this test.
124+
* @return auto-configuration exclusions to apply
125+
*/
126+
@AliasFor(annotation = ImportAutoConfiguration.class, attribute = "exclude")
127+
Class<?>[] excludeAutoConfiguration() default {};
128+
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webclient.test.autoconfigure;
18+
19+
import org.springframework.boot.test.autoconfigure.TestSliceTestContextBootstrapper;
20+
import org.springframework.test.context.TestContextBootstrapper;
21+
22+
/**
23+
* {@link TestContextBootstrapper} for {@link WebClientTest @WebClientTest} support.
24+
*
25+
* @author Andy Wilkinson
26+
*/
27+
class WebClientTestContextBootstrapper extends TestSliceTestContextBootstrapper<WebClientTest> {
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.webclient.test.autoconfigure;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.LinkedHashSet;
22+
import java.util.Set;
23+
24+
import org.springframework.boot.context.TypeExcludeFilter;
25+
import org.springframework.boot.jackson.JsonComponent;
26+
import org.springframework.boot.test.context.filter.annotation.StandardAnnotationCustomizableTypeExcludeFilter;
27+
import org.springframework.util.ClassUtils;
28+
29+
/**
30+
* {@link TypeExcludeFilter} for {@link WebClientTest @WebClientTest}.
31+
*
32+
* @author Andy Wilkinson
33+
*/
34+
class WebClientTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter<WebClientTest> {
35+
36+
private static final Class<?>[] NO_COMPONENTS = {};
37+
38+
private static final String DATABIND_MODULE_CLASS_NAME = "tools.jackson.databind.JacksonModule";
39+
40+
private static final Set<Class<?>> KNOWN_INCLUDES;
41+
42+
static {
43+
Set<Class<?>> includes = new LinkedHashSet<>();
44+
if (ClassUtils.isPresent(DATABIND_MODULE_CLASS_NAME, WebClientTypeExcludeFilter.class.getClassLoader())) {
45+
try {
46+
includes.add(Class.forName(DATABIND_MODULE_CLASS_NAME, true,
47+
WebClientTypeExcludeFilter.class.getClassLoader()));
48+
}
49+
catch (ClassNotFoundException ex) {
50+
throw new IllegalStateException("Failed to load " + DATABIND_MODULE_CLASS_NAME, ex);
51+
}
52+
includes.add(JsonComponent.class);
53+
}
54+
KNOWN_INCLUDES = Collections.unmodifiableSet(includes);
55+
}
56+
57+
private final Class<?>[] components;
58+
59+
WebClientTypeExcludeFilter(Class<?> testClass) {
60+
super(testClass);
61+
this.components = getAnnotation().getValue("components", Class[].class).orElse(NO_COMPONENTS);
62+
}
63+
64+
@Override
65+
protected Set<Class<?>> getKnownIncludes() {
66+
return KNOWN_INCLUDES;
67+
}
68+
69+
@Override
70+
protected Set<Class<?>> getComponentIncludes() {
71+
return new LinkedHashSet<>(Arrays.asList(this.components));
72+
}
73+
74+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for web clients.
19+
*/
20+
@NullMarked
21+
package org.springframework.boot.webclient.test.autoconfigure;
22+
23+
import org.jspecify.annotations.NullMarked;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.boot.http.codec.autoconfigure.CodecsAutoConfiguration
2+
org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration

0 commit comments

Comments
 (0)