Skip to content

Commit

Permalink
Add RestTestClient
Browse files Browse the repository at this point in the history
Fixes gh-31275

Signed-off-by: Rob Worsnop <[email protected]>
  • Loading branch information
rworsnop committed Feb 14, 2025
1 parent fe41cd6 commit 16c4260
Show file tree
Hide file tree
Showing 35 changed files with 5,871 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import java.nio.charset.StandardCharsets;
import java.util.List;

import jakarta.servlet.http.Cookie;
import org.jspecify.annotations.Nullable;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
Expand All @@ -31,6 +34,7 @@
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

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

try {
Cookie[] cookies = parseCookies(requestHeaders.get(HttpHeaders.COOKIE));
MockHttpServletRequestBuilder requestBuilder = request(httpMethod, uri)
.content(requestBody).headers(requestHeaders);
if (cookies.length > 0) {
requestBuilder.cookie(cookies);
}
MockHttpServletResponse servletResponse = this.mockMvc
.perform(request(httpMethod, uri).content(requestBody).headers(requestHeaders))
.perform(requestBuilder)
.andReturn()
.getResponse();

Expand All @@ -92,6 +102,22 @@ private ClientHttpResponse getClientHttpResponse(
}
}

private static Cookie[] parseCookies(@Nullable List<String> headerValues) {
if (headerValues == null) {
return new Cookie[0];
}
return headerValues.stream()
.flatMap(header -> StringUtils.commaDelimitedListToSet(header).stream())
.map(MockMvcClientHttpRequestFactory::parseCookie)
.toArray(Cookie[]::new);
}

private static Cookie parseCookie(String cookie) {
String[] parts = StringUtils.split(cookie, "=");
Assert.isTrue(parts != null && parts.length == 2, "Invalid cookie: '" + cookie + "'");
return new Cookie(parts[0], parts[1]);
}

private HttpHeaders getResponseHeaders(MockHttpServletResponse response) {
HttpHeaders headers = new HttpHeaders();
for (String name : response.getHeaderNames()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.test.web.server;

import jakarta.servlet.Filter;

import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;

/**
* Base class for implementations of {@link RestTestClient.MockMvcServerSpec}
* that simply delegates to a {@link org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder} supplied by
* the concrete subclasses.
*
* @author Rob Worsnop
* @param <B> the type of the concrete subclass spec
*/
abstract class AbstractMockMvcServerSpec<B extends RestTestClient.MockMvcServerSpec<B>>
implements RestTestClient.MockMvcServerSpec<B> {

@Override
public <T extends B> T filters(Filter... filters) {
getMockMvcBuilder().addFilters(filters);
return self();
}

@Override
public final <T extends B> T filter(Filter filter, String... urlPatterns) {
getMockMvcBuilder().addFilter(filter, urlPatterns);
return self();
}

@Override
public <T extends B> T defaultRequest(RequestBuilder requestBuilder) {
getMockMvcBuilder().defaultRequest(requestBuilder);
return self();
}

@Override
public <T extends B> T alwaysExpect(ResultMatcher resultMatcher) {
getMockMvcBuilder().alwaysExpect(resultMatcher);
return self();
}

@Override
public <T extends B> T dispatchOptions(boolean dispatchOptions) {
getMockMvcBuilder().dispatchOptions(dispatchOptions);
return self();
}

@Override
public <T extends B> T dispatcherServletCustomizer(DispatcherServletCustomizer customizer) {
getMockMvcBuilder().addDispatcherServletCustomizer(customizer);
return self();
}

@Override
public <T extends B> T apply(MockMvcConfigurer configurer) {
getMockMvcBuilder().apply(configurer);
return self();
}

@SuppressWarnings("unchecked")
private <T extends B> T self() {
return (T) this;
}

/**
* Return the concrete {@link ConfigurableMockMvcBuilder} to delegate
* configuration methods and to use to create the {@link MockMvc}.
*/
protected abstract ConfigurableMockMvcBuilder<?> getMockMvcBuilder();

@Override
public RestTestClient.Builder configureClient() {
MockMvc mockMvc = getMockMvcBuilder().build();
ClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
return RestTestClient.bindToServer(requestFactory);
}

@Override
public RestTestClient build() {
return configureClient().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.test.web.server;

import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
* Simple wrapper around a {@link DefaultMockMvcBuilder}.
*
* @author Rob Worsnop
*/
class ApplicationContextMockMvcSpec extends AbstractMockMvcServerSpec<ApplicationContextMockMvcSpec> {
private final DefaultMockMvcBuilder mockMvcBuilder;

public ApplicationContextMockMvcSpec(WebApplicationContext context) {
this.mockMvcBuilder = MockMvcBuilders.webAppContextSetup(context);
}

@Override
protected ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
return this.mockMvcBuilder;
}
}
Loading

0 comments on commit 16c4260

Please sign in to comment.