Skip to content

Commit 93f1516

Browse files
committed
Verify that WebClient hypermedia autoconfig works for WebTestClient.
* Write test cases that verify WebTestClient will pick up the same autoconfiguration beans created for WebClient. * Verify it handles one mediatype, multiple mediatypes, and custom mediatypes. Related: #16020. Resolves #20372.
1 parent 9bc160d commit 93f1516

File tree

5 files changed

+178
-9
lines changed

5 files changed

+178
-9
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2929
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
3030
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
3132
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
3234
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
3335
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3436
import org.springframework.boot.web.codec.CodecCustomizer;
@@ -42,12 +44,9 @@
4244
import org.springframework.hateoas.config.HypermediaMappingInformation;
4345
import org.springframework.http.codec.json.Jackson2JsonDecoder;
4446
import org.springframework.http.codec.json.Jackson2JsonEncoder;
45-
import org.springframework.plugin.core.Plugin;
4647
import org.springframework.util.Assert;
4748
import org.springframework.util.MimeType;
48-
import org.springframework.web.bind.annotation.RequestMapping;
4949
import org.springframework.web.reactive.function.client.WebClient;
50-
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
5150

5251
/**
5352
* {@link EnableAutoConfiguration Auto-configuration} for Spring HATEOAS's
@@ -60,10 +59,11 @@
6059
* @since 1.1.0
6160
*/
6261
@Configuration(proxyBeanMethods = false)
63-
@ConditionalOnClass({ EntityModel.class, RequestMapping.class, RequestMappingHandlerAdapter.class, Plugin.class })
62+
@ConditionalOnClass({ EntityModel.class })
6463
@ConditionalOnWebApplication
65-
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
66-
HttpMessageConvertersAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class })
64+
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class,
65+
HttpMessageConvertersAutoConfiguration.class, CodecsAutoConfiguration.class,
66+
RepositoryRestMvcAutoConfiguration.class })
6767
@EnableConfigurationProperties(HateoasProperties.class)
6868
@Import(HypermediaHttpMessageConverterConfiguration.class)
6969
public class HypermediaAutoConfiguration {

spring-boot-project/spring-boot-test-autoconfigure/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ dependencies {
7272
testImplementation("org.junit.platform:junit-platform-engine")
7373
testImplementation("org.junit.jupiter:junit-jupiter")
7474
testImplementation("org.mockito:mockito-core")
75-
testImplementation("org.skyscreamer:jsonassert")
7675
testImplementation("org.springframework.hateoas:spring-hateoas")
77-
testImplementation("org.springframework.plugin:spring-plugin-core")
76+
testImplementation("org.skyscreamer:jsonassert")
7877
testImplementation("org.testcontainers:junit-jupiter")
7978
testImplementation("org.testcontainers:neo4j")
8079
testImplementation("org.testcontainers:testcontainers")

spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/reactive/WebTestClientAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
2728
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
2829
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
2930
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -48,7 +49,8 @@
4849
*/
4950
@Configuration(proxyBeanMethods = false)
5051
@ConditionalOnClass({ WebClient.class, WebTestClient.class })
51-
@AutoConfigureAfter({ CodecsAutoConfiguration.class, WebFluxAutoConfiguration.class })
52+
@AutoConfigureAfter({ CodecsAutoConfiguration.class, WebFluxAutoConfiguration.class,
53+
HypermediaAutoConfiguration.class })
5254
@Import(WebTestClientSecurityConfiguration.class)
5355
@EnableConfigurationProperties
5456
public class WebTestClientAutoConfiguration {

spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
9696

9797
# AutoConfigureWebClient auto-configuration imports
9898
org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient=\
99+
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
99100
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
100101
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
101102
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
@@ -140,6 +141,7 @@ org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfigu
140141
org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\
141142
org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\
142143
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
144+
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
143145
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
144146
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
145147
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2012-2020 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.test.autoconfigure.hateoas;
18+
19+
import java.util.Collections;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
24+
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
26+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
27+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
28+
import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration;
29+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.hateoas.MediaTypes;
33+
import org.springframework.hateoas.config.EnableHypermediaSupport;
34+
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
35+
import org.springframework.hateoas.config.HypermediaMappingInformation;
36+
import org.springframework.http.MediaType;
37+
import org.springframework.http.codec.HttpMessageReader;
38+
import org.springframework.http.codec.HttpMessageWriter;
39+
import org.springframework.test.util.ReflectionTestUtils;
40+
import org.springframework.test.web.reactive.server.WebTestClient;
41+
import org.springframework.web.reactive.config.EnableWebFlux;
42+
import org.springframework.web.reactive.function.client.ExchangeStrategies;
43+
import org.springframework.web.reactive.function.client.WebClient;
44+
45+
import static org.assertj.core.api.Assertions.assertThat;
46+
47+
/**
48+
* Tests for hypermedia-based {@link WebTestClient}.
49+
*
50+
* @author Brian Clozel
51+
* @author Stephane Nicoll
52+
* @author Greg Turnquist
53+
*/
54+
class HypermediaWebTestClientAutoConfigurationTests {
55+
56+
static MediaType FRODO_JSON = MediaType.parseMediaType("application/frodo+json");
57+
58+
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
59+
.withUserConfiguration(BaseConfiguration.class);
60+
61+
@Test
62+
void codecsCustomizerShouldRegisterHypermediaTypesWithWebTestClient() {
63+
this.contextRunner.withUserConfiguration(HalConfig.class).run((context) -> {
64+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
65+
66+
assertThat(exchangeStrategies(webTestClient).messageReaders())
67+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON)
68+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
69+
assertThat(exchangeStrategies(webTestClient).messageWriters())
70+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON)
71+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
72+
});
73+
}
74+
75+
@Test
76+
void codecsCustomizerShouldRegisterAlternativeHypermediaTypesWithWebTestClient() {
77+
this.contextRunner.withUserConfiguration(HalFormsConfig.class).run((context) -> {
78+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
79+
80+
assertThat(exchangeStrategies(webTestClient).messageReaders())
81+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_FORMS_JSON)
82+
.doesNotContain(MediaTypes.HAL_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
83+
assertThat(exchangeStrategies(webTestClient).messageWriters())
84+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_FORMS_JSON)
85+
.doesNotContain(MediaTypes.HAL_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
86+
});
87+
}
88+
89+
@Test
90+
void codecsCustomizerShouldRegisterAllHypermediaTypesWithWebTestClient() {
91+
this.contextRunner.withUserConfiguration(AllHypermediaConfig.class).run((context) -> {
92+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
93+
94+
assertThat(exchangeStrategies(webTestClient).messageReaders())
95+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON,
96+
MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
97+
assertThat(exchangeStrategies(webTestClient).messageWriters())
98+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON,
99+
MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
100+
});
101+
}
102+
103+
@Test
104+
void codecsCustomizerShouldRegisterCustomHypermediaTypesWithWebTestClient() {
105+
this.contextRunner.withUserConfiguration(CustomHypermediaConfig.class).run((context) -> {
106+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
107+
108+
assertThat(exchangeStrategies(webTestClient).messageReaders())
109+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON, FRODO_JSON)
110+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
111+
assertThat(exchangeStrategies(webTestClient).messageWriters())
112+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON, FRODO_JSON)
113+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
114+
});
115+
}
116+
117+
/**
118+
* Extract the {@link ExchangeStrategies} from a {@link WebTestClient} to assert it
119+
* has the proper message readers and writers.
120+
* @param webTestClient
121+
* @return
122+
*/
123+
private static ExchangeStrategies exchangeStrategies(WebTestClient webTestClient) {
124+
WebClient webClient = (WebClient) ReflectionTestUtils.getField(webTestClient, "webClient");
125+
return (ExchangeStrategies) ReflectionTestUtils
126+
.getField(ReflectionTestUtils.getField(webClient, "exchangeFunction"), "strategies");
127+
}
128+
129+
@ImportAutoConfiguration({ CodecsAutoConfiguration.class, WebFluxAutoConfiguration.class,
130+
JacksonAutoConfiguration.class, HypermediaAutoConfiguration.class, WebTestClientAutoConfiguration.class })
131+
@EnableWebFlux
132+
static class BaseConfiguration {
133+
134+
}
135+
136+
@Configuration(proxyBeanMethods = false)
137+
@EnableHypermediaSupport(type = HypermediaType.HAL)
138+
static class HalConfig {
139+
140+
}
141+
142+
@Configuration(proxyBeanMethods = false)
143+
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
144+
static class HalFormsConfig {
145+
146+
}
147+
148+
@Configuration(proxyBeanMethods = false)
149+
@EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON,
150+
HypermediaType.UBER })
151+
static class AllHypermediaConfig {
152+
153+
}
154+
155+
@Configuration(proxyBeanMethods = false)
156+
@EnableHypermediaSupport(type = HypermediaType.HAL)
157+
static class CustomHypermediaConfig {
158+
159+
@Bean
160+
HypermediaMappingInformation frodoMediaType() {
161+
return () -> Collections.singletonList(FRODO_JSON);
162+
}
163+
164+
}
165+
166+
}

0 commit comments

Comments
 (0)