Skip to content

Commit 5c22468

Browse files
committed
Add hypermedia autoconfiguration for WebTestClient.
* Provide CodecCustomizer for WebTestClient. Related: spring-projects#16020.
1 parent 533a20a commit 5c22468

File tree

8 files changed

+240
-8
lines changed

8 files changed

+240
-8
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
package org.springframework.boot.autoconfigure.hateoas;
1818

19-
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import java.util.List;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2223
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -28,23 +29,36 @@
2829
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
2930
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
3031
import org.springframework.boot.context.properties.EnableConfigurationProperties;
32+
import org.springframework.boot.web.client.RestTemplateCustomizer;
33+
import org.springframework.boot.web.codec.CodecCustomizer;
34+
import org.springframework.context.annotation.Bean;
3135
import org.springframework.context.annotation.Configuration;
3236
import org.springframework.context.annotation.Import;
3337
import org.springframework.hateoas.EntityModel;
3438
import org.springframework.hateoas.client.LinkDiscoverers;
3539
import org.springframework.hateoas.config.EnableHypermediaSupport;
3640
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
41+
import org.springframework.hateoas.config.HypermediaMappingInformation;
42+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
43+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
3744
import org.springframework.plugin.core.Plugin;
45+
import org.springframework.util.Assert;
46+
import org.springframework.util.MimeType;
3847
import org.springframework.web.bind.annotation.RequestMapping;
48+
import org.springframework.web.client.RestTemplate;
49+
import org.springframework.web.reactive.function.client.WebClient;
3950
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
4051

52+
import com.fasterxml.jackson.databind.ObjectMapper;
53+
4154
/**
4255
* {@link EnableAutoConfiguration Auto-configuration} for Spring HATEOAS's
4356
* {@link EnableHypermediaSupport @EnableHypermediaSupport}.
4457
*
4558
* @author Roy Clarkson
4659
* @author Oliver Gierke
4760
* @author Andy Wilkinson
61+
* @author Greg Turnquist
4862
* @since 1.1.0
4963
*/
5064
@Configuration(proxyBeanMethods = false)
@@ -63,5 +77,5 @@ public class HypermediaAutoConfiguration {
6377
protected static class HypermediaConfiguration {
6478

6579
}
66-
80+
6781
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3131
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
32+
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
3233
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
3334
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
3435
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.NotReactiveWebApplicationCondition;
@@ -49,7 +50,7 @@
4950
* @since 1.4.0
5051
*/
5152
@Configuration(proxyBeanMethods = false)
52-
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)
53+
@AutoConfigureAfter({ HttpMessageConvertersAutoConfiguration.class, HypermediaAutoConfiguration.class })
5354
@ConditionalOnClass(RestTemplate.class)
5455
@Conditional(NotReactiveWebApplicationCondition.class)
5556
public class RestTemplateAutoConfiguration {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
package org.springframework.boot.autoconfigure.hateoas;
1818

19+
import static org.assertj.core.api.Assertions.*;
20+
1921
import java.util.Optional;
2022

2123
import org.junit.jupiter.api.Test;
22-
2324
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
2425
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration.HypermediaConfiguration;
2526
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@@ -40,15 +41,14 @@
4041
import org.springframework.http.converter.HttpMessageConverter;
4142
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
4243

43-
import static org.assertj.core.api.Assertions.assertThat;
44-
4544
/**
4645
* Tests for {@link HypermediaAutoConfiguration}.
4746
*
4847
* @author Roy Clarkson
4948
* @author Oliver Gierke
5049
* @author Andy Wilkinson
5150
* @author Madhura Bhave
51+
* @author Greg Turnquist
5252
*/
5353
class HypermediaAutoConfigurationTests {
5454

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
optional("org.springframework.data:spring-data-neo4j")
4141
optional("org.springframework.data:spring-data-r2dbc")
4242
optional("org.springframework.data:spring-data-redis")
43+
optional("org.springframework.hateoas:spring-hateoas")
4344
optional("org.springframework.restdocs:spring-restdocs-mockmvc")
4445
optional("org.springframework.restdocs:spring-restdocs-restassured")
4546
optional("org.springframework.restdocs:spring-restdocs-webtestclient")
@@ -73,8 +74,6 @@ dependencies {
7374
testImplementation("org.junit.jupiter:junit-jupiter")
7475
testImplementation("org.mockito:mockito-core")
7576
testImplementation("org.skyscreamer:jsonassert")
76-
testImplementation("org.springframework.hateoas:spring-hateoas")
77-
testImplementation("org.springframework.plugin:spring-plugin-core")
7877
testImplementation("org.testcontainers:junit-jupiter")
7978
testImplementation("org.testcontainers:neo4j")
8079
testImplementation("org.testcontainers:testcontainers")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 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+
package org.springframework.boot.test.autoconfigure.hateoas;
17+
18+
import java.util.List;
19+
20+
import org.springframework.beans.factory.ObjectProvider;
21+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
25+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
26+
import org.springframework.boot.web.codec.CodecCustomizer;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.hateoas.config.HypermediaMappingInformation;
30+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
31+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
32+
import org.springframework.test.web.reactive.server.WebTestClient;
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.MimeType;
35+
36+
import com.fasterxml.jackson.databind.ObjectMapper;
37+
38+
/**
39+
* @author Greg Turnquist
40+
*/
41+
@Configuration(proxyBeanMethods = false)
42+
@AutoConfigureAfter({CodecsAutoConfiguration.class})
43+
@EnableConfigurationProperties
44+
public class HypermediaTestAutoConfiguration {
45+
46+
@Bean
47+
@ConditionalOnMissingBean
48+
@ConditionalOnClass({WebTestClient.class})
49+
CodecCustomizer hypermediaCodecCustomizer(ObjectProvider<ObjectMapper> mapperProvider,
50+
List<HypermediaMappingInformation> hypermediaTypes) {
51+
return codecConfigurer -> {
52+
Assert.notNull(hypermediaTypes, "HypermediaMappingInformations must not be null!");
53+
54+
hypermediaTypes.forEach(hypermedia -> {
55+
56+
ObjectMapper objectMapper = hypermedia
57+
.configureObjectMapper(mapperProvider.getIfAvailable(ObjectMapper::new).copy());
58+
MimeType[] mimeTypes = hypermedia.getMediaTypes().toArray(new MimeType[0]);
59+
60+
codecConfigurer.customCodecs()
61+
.registerWithDefaultConfig(new Jackson2JsonEncoder(objectMapper, mimeTypes));
62+
codecConfigurer.customCodecs()
63+
.registerWithDefaultConfig(new Jackson2JsonDecoder(objectMapper, mimeTypes));
64+
});
65+
};
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 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+
/**
18+
* Auto-configuration for Spring HATEOAS tests.
19+
*/
20+
package org.springframework.boot.test.autoconfigure.hateoas;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveO
100100
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
101101
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
102102
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
103+
org.springframework.boot.test.autoconfigure.hateoas.HypermediaTestAutoConfiguration,\
103104
org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration
104105

105106
# AutoConfigureWebFlux auto-configuration imports
@@ -182,3 +183,5 @@ org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListen
182183
org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener,\
183184
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\
184185
org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener
186+
187+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 static org.assertj.core.api.Assertions.*;
20+
import static org.mockito.Mockito.*;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.springframework.boot.autoconfigure.AutoConfigurations;
24+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
25+
import org.springframework.boot.test.autoconfigure.web.reactive.WebTestClientAutoConfiguration;
26+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
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.http.MediaType;
33+
import org.springframework.http.codec.HttpMessageReader;
34+
import org.springframework.http.codec.HttpMessageWriter;
35+
import org.springframework.test.util.ReflectionTestUtils;
36+
import org.springframework.test.web.reactive.server.WebTestClient;
37+
import org.springframework.web.reactive.function.client.ExchangeStrategies;
38+
import org.springframework.web.reactive.function.client.WebClient;
39+
import org.springframework.web.server.WebHandler;
40+
41+
/**
42+
* Tests for hypermedia-based {@link WebTestClient}.
43+
*
44+
* @author Brian Clozel
45+
* @author Stephane Nicoll
46+
* @author Greg Turnquist
47+
*/
48+
class HypermediaTestAutoConfigurationTests {
49+
50+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
51+
.withConfiguration(AutoConfigurations.of(WebTestClientAutoConfiguration.class));
52+
53+
@Test
54+
void codecsCustomizerShouldRegisterHypermediaTypesWithWebTestClient() {
55+
this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> {
56+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
57+
assertWebTestClientHasHypermedia(webTestClient, MediaTypes.HAL_JSON);
58+
});
59+
}
60+
61+
@Test
62+
void codecsCustomizerShouldRegisterAlternativeHypermediaTypesWithWebTestClient() {
63+
this.contextRunner.withUserConfiguration(HalFormsConfiguration.class).run((context) -> {
64+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
65+
assertWebTestClientHasHypermedia(webTestClient, MediaTypes.HAL_FORMS_JSON);
66+
});
67+
}
68+
69+
@Test
70+
void codecsCustomizerShouldRegisterAllHypermediaTypesWithWebTestClient() {
71+
this.contextRunner.withUserConfiguration(AllHypermediaTypesConfiguration.class).run((context) -> {
72+
WebTestClient webTestClient = context.getBean(WebTestClient.class);
73+
assertWebTestClientHasHypermedia(webTestClient, MediaTypes.HAL_JSON, MediaTypes.HAL_FORMS_JSON,
74+
MediaTypes.COLLECTION_JSON, MediaTypes.UBER_JSON);
75+
});
76+
}
77+
78+
private static void assertWebTestClientHasHypermedia(WebTestClient webTestClient, MediaType... mediaTypes) {
79+
80+
WebClient webClient = (WebClient) ReflectionTestUtils.getField(webTestClient, "webClient");
81+
ExchangeStrategies strategies = (ExchangeStrategies) ReflectionTestUtils
82+
.getField(ReflectionTestUtils.getField(webClient, "exchangeFunction"), "strategies");
83+
84+
assertThat(strategies.messageReaders()).flatExtracting(HttpMessageReader::getReadableMediaTypes)
85+
.contains(mediaTypes);
86+
assertThat(strategies.messageWriters()).flatExtracting(HttpMessageWriter::getWritableMediaTypes)
87+
.contains(mediaTypes);
88+
}
89+
90+
@ImportAutoConfiguration(HypermediaTestAutoConfiguration.class)
91+
@Configuration(proxyBeanMethods = false)
92+
@EnableHypermediaSupport(type = HypermediaType.HAL)
93+
static class BaseConfiguration {
94+
95+
@Bean
96+
WebHandler webHandler() {
97+
return mock(WebHandler.class);
98+
}
99+
100+
}
101+
102+
@ImportAutoConfiguration(HypermediaTestAutoConfiguration.class)
103+
@Configuration(proxyBeanMethods = false)
104+
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
105+
static class HalFormsConfiguration {
106+
107+
@Bean
108+
WebHandler webHandler() {
109+
return mock(WebHandler.class);
110+
}
111+
112+
}
113+
114+
@ImportAutoConfiguration(HypermediaTestAutoConfiguration.class)
115+
@Configuration(proxyBeanMethods = false)
116+
@EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON,
117+
HypermediaType.UBER })
118+
static class AllHypermediaTypesConfiguration {
119+
120+
@Bean
121+
WebHandler webHandler() {
122+
return mock(WebHandler.class);
123+
}
124+
125+
}
126+
127+
}

0 commit comments

Comments
 (0)