Browse Source
This commit adds a new `ResponseCreator` implementation that uses a `ClientHttpRequestFactory` to perform an actual request. Closes gh-29721pull/29796/head
5 changed files with 300 additions and 0 deletions
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2002-2023 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.client.response; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.http.client.ClientHttpRequest; |
||||
import org.springframework.http.client.ClientHttpRequestFactory; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.mock.http.client.MockClientHttpRequest; |
||||
import org.springframework.test.web.client.ResponseCreator; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StreamUtils; |
||||
|
||||
/** |
||||
* A {@code ResponseCreator} which delegates to a {@link ClientHttpRequestFactory} |
||||
* to perform the request and return the associated response. |
||||
* This is notably useful when testing code that calls multiple remote services, some |
||||
* of which need to be actually called rather than further mocked. |
||||
* <p>Note that the input request is asserted to be a {@code MockClientHttpRequest} and |
||||
* the URI, method, headers and body are copied. |
||||
* <p>The factory can typically be obtained from a {@code RestTemplate} but in case this |
||||
* is used with e.g. {@code MockRestServiceServer}, make sure to capture the factory early |
||||
* before binding the mock server to the RestTemplate (as it replaces the factory): |
||||
* <pre><code> |
||||
* ResponseCreator withActualResponse = new ExecutingResponseCreator(restTemplate); |
||||
* MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build(); |
||||
* //...
|
||||
* server.expect(requestTo("/foo")).andRespond(withSuccess()); |
||||
* server.expect(requestTo("/bar")).andRespond(withActualResponse); |
||||
* </code></pre> |
||||
* |
||||
* @author Simon Baslé |
||||
* @since 6.0.4 |
||||
*/ |
||||
public class ExecutingResponseCreator implements ResponseCreator { |
||||
|
||||
private final ClientHttpRequestFactory requestFactory; |
||||
|
||||
|
||||
/** |
||||
* Create a {@code ExecutingResponseCreator} from a {@code ClientHttpRequestFactory}. |
||||
* @param requestFactory the request factory to delegate to |
||||
*/ |
||||
public ExecutingResponseCreator(ClientHttpRequestFactory requestFactory) { |
||||
this.requestFactory = requestFactory; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { |
||||
Assert.state(request instanceof MockClientHttpRequest, "Request should be an instance of MockClientHttpRequest"); |
||||
MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; |
||||
ClientHttpRequest newRequest = this.requestFactory.createRequest(mockRequest.getURI(), mockRequest.getMethod()); |
||||
newRequest.getHeaders().putAll(mockRequest.getHeaders()); |
||||
StreamUtils.copy(mockRequest.getBodyAsBytes(), newRequest.getBody()); |
||||
return newRequest.execute(); |
||||
} |
||||
} |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* Copyright 2002-2023 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.client.response; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.net.URI; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.client.AbstractClientHttpRequest; |
||||
import org.springframework.http.client.ClientHttpRequest; |
||||
import org.springframework.http.client.ClientHttpRequestFactory; |
||||
import org.springframework.http.client.ClientHttpResponse; |
||||
import org.springframework.mock.http.client.MockClientHttpRequest; |
||||
import org.springframework.mock.http.client.MockClientHttpResponse; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for the {@link ExecutingResponseCreator} implementation. |
||||
* |
||||
* @author Simon Baslé |
||||
*/ |
||||
class ExecutingResponseCreatorTests { |
||||
|
||||
@Test |
||||
void ensureRequestNotNull() { |
||||
final ExecutingResponseCreator responseCreator = new ExecutingResponseCreator((uri, method) -> null); |
||||
|
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> responseCreator.createResponse(null)) |
||||
.withMessage("Request should be an instance of MockClientHttpRequest"); |
||||
} |
||||
|
||||
@Test |
||||
void ensureRequestIsMock() { |
||||
final ExecutingResponseCreator responseCreator = new ExecutingResponseCreator((uri, method) -> null); |
||||
ClientHttpRequest notAMockRequest = new AbstractClientHttpRequest() { |
||||
@Override |
||||
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpMethod getMethod() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public URI getURI() { |
||||
return null; |
||||
} |
||||
}; |
||||
|
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> responseCreator.createResponse(notAMockRequest)) |
||||
.withMessage("Request should be an instance of MockClientHttpRequest"); |
||||
} |
||||
|
||||
@Test |
||||
void requestIsCopied() throws IOException { |
||||
MockClientHttpRequest originalRequest = new MockClientHttpRequest(HttpMethod.POST, |
||||
"https://example.org"); |
||||
String body = "original body"; |
||||
originalRequest.getHeaders().add("X-example", "original"); |
||||
originalRequest.getBody().write(body.getBytes(StandardCharsets.UTF_8)); |
||||
MockClientHttpResponse originalResponse = new MockClientHttpResponse(new byte[0], 500); |
||||
List<MockClientHttpRequest> factoryRequests = new ArrayList<>(); |
||||
ClientHttpRequestFactory originalFactory = (uri, httpMethod) -> { |
||||
MockClientHttpRequest request = new MockClientHttpRequest(httpMethod, uri); |
||||
request.setResponse(originalResponse); |
||||
factoryRequests.add(request); |
||||
return request; |
||||
}; |
||||
|
||||
final ExecutingResponseCreator responseCreator = new ExecutingResponseCreator(originalFactory); |
||||
final ClientHttpResponse response = responseCreator.createResponse(originalRequest); |
||||
|
||||
assertThat(response).as("response").isSameAs(originalResponse); |
||||
assertThat(originalRequest.isExecuted()).as("originalRequest.isExecuted").isFalse(); |
||||
|
||||
assertThat(factoryRequests) |
||||
.hasSize(1) |
||||
.first() |
||||
.isNotSameAs(originalRequest) |
||||
.satisfies(copiedRequest -> { |
||||
assertThat(copiedRequest) |
||||
.as("copied request") |
||||
.isNotSameAs(originalRequest); |
||||
assertThat(copiedRequest.isExecuted()) |
||||
.as("copiedRequest.isExecuted").isTrue(); |
||||
assertThat(copiedRequest.getBody()) |
||||
.as("copiedRequest.body").isNotSameAs(originalRequest.getBody()); |
||||
assertThat(copiedRequest.getHeaders()) |
||||
.as("copiedRequest.headers").isNotSameAs(originalRequest.getHeaders()); |
||||
}); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue