Skip to content

Commit d239e30

Browse files
committed
Add RestTestClient
Fixes spring-projectsgh-31275
1 parent fe41cd6 commit d239e30

35 files changed

+5871
-2
lines changed

spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java

+27-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.List;
2222

23+
import jakarta.servlet.http.Cookie;
24+
import org.jspecify.annotations.Nullable;
25+
2326
import org.springframework.http.HttpHeaders;
2427
import org.springframework.http.HttpMethod;
2528
import org.springframework.http.HttpStatus;
@@ -31,6 +34,7 @@
3134
import org.springframework.mock.http.client.MockClientHttpResponse;
3235
import org.springframework.mock.web.MockHttpServletResponse;
3336
import org.springframework.test.web.servlet.MockMvc;
37+
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
3438
import org.springframework.util.Assert;
3539
import org.springframework.util.StringUtils;
3640

@@ -67,8 +71,14 @@ private ClientHttpResponse getClientHttpResponse(
6771
HttpMethod httpMethod, URI uri, HttpHeaders requestHeaders, byte[] requestBody) {
6872

6973
try {
74+
Cookie[] cookies = parseCookies(requestHeaders.get(HttpHeaders.COOKIE));
75+
MockHttpServletRequestBuilder requestBuilder = request(httpMethod, uri)
76+
.content(requestBody).headers(requestHeaders);
77+
if (cookies.length > 0) {
78+
requestBuilder.cookie(cookies);
79+
}
7080
MockHttpServletResponse servletResponse = this.mockMvc
71-
.perform(request(httpMethod, uri).content(requestBody).headers(requestHeaders))
81+
.perform(requestBuilder)
7282
.andReturn()
7383
.getResponse();
7484

@@ -92,6 +102,22 @@ private ClientHttpResponse getClientHttpResponse(
92102
}
93103
}
94104

105+
private static Cookie[] parseCookies(@Nullable List<String> headerValues) {
106+
if (headerValues == null) {
107+
return new Cookie[0];
108+
}
109+
return headerValues.stream()
110+
.flatMap(header -> StringUtils.commaDelimitedListToSet(header).stream())
111+
.map(MockMvcClientHttpRequestFactory::parseCookie)
112+
.toArray(Cookie[]::new);
113+
}
114+
115+
private static Cookie parseCookie(String cookie) {
116+
String[] parts = StringUtils.split(cookie, "=");
117+
Assert.isTrue(parts != null && parts.length == 2, "Invalid cookie: '" + cookie + "'");
118+
return new Cookie(parts[0], parts[1]);
119+
}
120+
95121
private HttpHeaders getResponseHeaders(MockHttpServletResponse response) {
96122
HttpHeaders headers = new HttpHeaders();
97123
for (String name : response.getHeaderNames()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2025 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.test.web.server;
18+
19+
import jakarta.servlet.Filter;
20+
21+
import org.springframework.http.client.ClientHttpRequestFactory;
22+
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
23+
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
24+
import org.springframework.test.web.servlet.MockMvc;
25+
import org.springframework.test.web.servlet.RequestBuilder;
26+
import org.springframework.test.web.servlet.ResultMatcher;
27+
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
28+
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
29+
30+
/**
31+
* Base class for implementations of {@link RestTestClient.MockMvcServerSpec}
32+
* that simply delegates to a {@link org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder} supplied by
33+
* the concrete subclasses.
34+
*
35+
* @author Rob Worsnop
36+
* @param <B> the type of the concrete subclass spec
37+
*/
38+
abstract class AbstractMockMvcServerSpec<B extends RestTestClient.MockMvcServerSpec<B>>
39+
implements RestTestClient.MockMvcServerSpec<B> {
40+
41+
@Override
42+
public <T extends B> T filters(Filter... filters) {
43+
getMockMvcBuilder().addFilters(filters);
44+
return self();
45+
}
46+
47+
@Override
48+
public final <T extends B> T filter(Filter filter, String... urlPatterns) {
49+
getMockMvcBuilder().addFilter(filter, urlPatterns);
50+
return self();
51+
}
52+
53+
@Override
54+
public <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
55+
getMockMvcBuilder().defaultRequest(requestBuilder);
56+
return self();
57+
}
58+
59+
@Override
60+
public <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
61+
getMockMvcBuilder().alwaysExpect(resultMatcher);
62+
return self();
63+
}
64+
65+
@Override
66+
public <T extends B> T dispatchOptions(boolean dispatchOptions) {
67+
getMockMvcBuilder().dispatchOptions(dispatchOptions);
68+
return self();
69+
}
70+
71+
@Override
72+
public <T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
73+
getMockMvcBuilder().addDispatcherServletCustomizer(customizer);
74+
return self();
75+
}
76+
77+
@Override
78+
public <T extends B> T apply(MockMvcConfigurer configurer) {
79+
getMockMvcBuilder().apply(configurer);
80+
return self();
81+
}
82+
83+
@SuppressWarnings("unchecked")
84+
private <T extends B> T self() {
85+
return (T) this;
86+
}
87+
88+
/**
89+
* Return the concrete {@link ConfigurableMockMvcBuilder} to delegate
90+
* configuration methods and to use to create the {@link MockMvc}.
91+
*/
92+
protected abstract ConfigurableMockMvcBuilder<?> getMockMvcBuilder();
93+
94+
@Override
95+
public RestTestClient.Builder configureClient() {
96+
MockMvc mockMvc = getMockMvcBuilder().build();
97+
ClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
98+
return RestTestClient.bindToServer(requestFactory);
99+
}
100+
101+
@Override
102+
public RestTestClient build() {
103+
return configureClient().build();
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2025 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.test.web.server;
18+
19+
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
20+
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
21+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
22+
import org.springframework.web.context.WebApplicationContext;
23+
24+
/**
25+
* Simple wrapper around a {@link DefaultMockMvcBuilder}.
26+
*
27+
* @author Rob Worsnop
28+
*/
29+
class ApplicationContextMockMvcSpec extends AbstractMockMvcServerSpec<ApplicationContextMockMvcSpec> {
30+
private final DefaultMockMvcBuilder mockMvcBuilder;
31+
32+
public ApplicationContextMockMvcSpec(WebApplicationContext context) {
33+
this.mockMvcBuilder = MockMvcBuilders.webAppContextSetup(context);
34+
}
35+
36+
@Override
37+
protected ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
38+
return this.mockMvcBuilder;
39+
}
40+
}

0 commit comments

Comments
 (0)