Skip to content

Commit 91b181c

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: spring-projects#16020. Resolves spring-projects#20372.
1 parent 9bc160d commit 91b181c

File tree

5 files changed

+173
-5
lines changed

5 files changed

+173
-5
lines changed

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

Lines changed: 5 additions & 2 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;
@@ -62,8 +64,9 @@
6264
@Configuration(proxyBeanMethods = false)
6365
@ConditionalOnClass({ EntityModel.class, RequestMapping.class, RequestMappingHandlerAdapter.class, Plugin.class })
6466
@ConditionalOnWebApplication
65-
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
66-
HttpMessageConvertersAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class })
67+
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class,
68+
HttpMessageConvertersAutoConfiguration.class, CodecsAutoConfiguration.class,
69+
RepositoryRestMvcAutoConfiguration.class })
6770
@EnableConfigurationProperties(HateoasProperties.class)
6871
@Import(HypermediaHttpMessageConverterConfiguration.class)
6972
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,162 @@
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.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration;
26+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.hateoas.MediaTypes;
30+
import org.springframework.hateoas.config.EnableHypermediaSupport;
31+
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
32+
import org.springframework.hateoas.config.HypermediaMappingInformation;
33+
import org.springframework.http.MediaType;
34+
import org.springframework.http.codec.HttpMessageReader;
35+
import org.springframework.http.codec.HttpMessageWriter;
36+
import org.springframework.test.util.ReflectionTestUtils;
37+
import org.springframework.test.web.reactive.server.WebTestClient;
38+
import org.springframework.web.reactive.config.EnableWebFlux;
39+
import org.springframework.web.reactive.function.client.ExchangeStrategies;
40+
import org.springframework.web.reactive.function.client.WebClient;
41+
42+
import static org.assertj.core.api.Assertions.assertThat;
43+
44+
/**
45+
* Tests for hypermedia-based {@link WebTestClient}.
46+
*
47+
* @author Brian Clozel
48+
* @author Stephane Nicoll
49+
* @author Greg Turnquist
50+
*/
51+
class HypermediaWebTestClientAutoConfigurationTests {
52+
53+
static MediaType FRODO_JSON = MediaType.parseMediaType("application/frodo+json");
54+
55+
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
56+
.withUserConfiguration(BaseConfiguration.class);
57+
58+
@Test
59+
void codecsCustomizerShouldRegisterHypermediaTypesWithWebTestClient() {
60+
this.contextRunner.withUserConfiguration(HalConfig.class).run((context) -> {
61+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
62+
63+
assertThat(exchangeStrategies(webTestClient).messageReaders())
64+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON)
65+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
66+
assertThat(exchangeStrategies(webTestClient).messageWriters())
67+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON)
68+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
69+
});
70+
}
71+
72+
@Test
73+
void codecsCustomizerShouldRegisterAlternativeHypermediaTypesWithWebTestClient() {
74+
this.contextRunner.withUserConfiguration(HalFormsConfig.class).run((context) -> {
75+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
76+
77+
assertThat(exchangeStrategies(webTestClient).messageReaders())
78+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_FORMS_JSON)
79+
.doesNotContain(MediaTypes.HAL_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
80+
assertThat(exchangeStrategies(webTestClient).messageWriters())
81+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_FORMS_JSON)
82+
.doesNotContain(MediaTypes.HAL_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
83+
});
84+
}
85+
86+
@Test
87+
void codecsCustomizerShouldRegisterAllHypermediaTypesWithWebTestClient() {
88+
this.contextRunner.withUserConfiguration(AllHypermediaConfig.class).run((context) -> {
89+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
90+
91+
assertThat(exchangeStrategies(webTestClient).messageReaders())
92+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON,
93+
MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
94+
assertThat(exchangeStrategies(webTestClient).messageWriters())
95+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON,
96+
MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
97+
});
98+
}
99+
100+
@Test
101+
void codecsCustomizerShouldRegisterCustomHypermediaTypesWithWebTestClient() {
102+
this.contextRunner.withUserConfiguration(CustomHypermediaConfig.class).run((context) -> {
103+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
104+
105+
assertThat(exchangeStrategies(webTestClient).messageReaders())
106+
.flatExtracting(HttpMessageReader::getReadableMediaTypes).contains(MediaTypes.HAL_JSON, FRODO_JSON)
107+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
108+
assertThat(exchangeStrategies(webTestClient).messageWriters())
109+
.flatExtracting(HttpMessageWriter::getWritableMediaTypes).contains(MediaTypes.HAL_JSON, FRODO_JSON)
110+
.doesNotContain(MediaTypes.HAL_FORMS_JSON, MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
111+
});
112+
}
113+
114+
/**
115+
* Extract the {@link ExchangeStrategies} from a {@link WebTestClient} to assert it
116+
* has the proper message readers and writers.
117+
* @param webTestClient
118+
* @return
119+
*/
120+
private static ExchangeStrategies exchangeStrategies(WebTestClient webTestClient) {
121+
WebClient webClient = (WebClient) ReflectionTestUtils.getField(webTestClient, "webClient");
122+
return (ExchangeStrategies) ReflectionTestUtils
123+
.getField(ReflectionTestUtils.getField(webClient, "exchangeFunction"), "strategies");
124+
}
125+
126+
@ImportAutoConfiguration({ HypermediaAutoConfiguration.class, WebTestClientAutoConfiguration.class })
127+
@EnableWebFlux
128+
static class BaseConfiguration {
129+
130+
}
131+
132+
@Configuration(proxyBeanMethods = false)
133+
@EnableHypermediaSupport(type = HypermediaType.HAL)
134+
static class HalConfig {
135+
136+
}
137+
138+
@Configuration(proxyBeanMethods = false)
139+
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
140+
static class HalFormsConfig {
141+
142+
}
143+
144+
@Configuration(proxyBeanMethods = false)
145+
@EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON,
146+
HypermediaType.UBER })
147+
static class AllHypermediaConfig {
148+
149+
}
150+
151+
@Configuration(proxyBeanMethods = false)
152+
@EnableHypermediaSupport(type = HypermediaType.HAL)
153+
static class CustomHypermediaConfig {
154+
155+
@Bean
156+
HypermediaMappingInformation frodoMediaType() {
157+
return () -> Collections.singletonList(FRODO_JSON);
158+
}
159+
160+
}
161+
162+
}

0 commit comments

Comments
 (0)