From c702cd418ed8336366a85f83785e01b7e307108f Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 6 Jan 2023 16:50:19 +0000 Subject: [PATCH] Polishing See gh-29721 --- .../testing/spring-mvc-test-client.adoc | 25 ++++---- .../response/ExecutingResponseCreator.java | 19 +++--- .../response/MockRestResponseCreators.java | 15 ++--- .../client/MockRestServiceServerTests.java | 43 ++++++-------- .../ExecutingResponseCreatorTests.java | 59 +++++-------------- 5 files changed, 59 insertions(+), 102 deletions(-) diff --git a/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc index 83f09bd3e2b..1bbd496e264 100644 --- a/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc +++ b/framework-docs/src/docs/asciidoc/testing/spring-mvc-test-client.adoc @@ -117,21 +117,21 @@ logic but without running a server. The following example shows how to do so: // Test code that uses the above RestTemplate ... ---- -In the more specific cases where total isolation isn't desired and some integration testing -of one or more calls is needed, a specific `ResponseCreator` can be set up in advance and -used to perform actual requests and assert the response. -The following example shows how to set up and use the `ExecutingResponseCreator` to do so: +In some cases it may be necessary to perform an actual call to a remote service instead +of mocking the response. The following example shows how to do that through +`ExecutingResponseCreator`: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- RestTemplate restTemplate = new RestTemplate(); - // Make sure to capture the request factory of the RestTemplate before binding + // Create ExecutingResponseCreator with the original request factory ExecutingResponseCreator withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory()); MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); - mockServer.expect(requestTo("/greeting")).andRespond(withActualResponse); + mockServer.expect(requestTo("/profile")).andRespond(withSuccess()); + mockServer.expect(requestTo("/quoteOfTheDay")).andRespond(withActualResponse); // Test code that uses the above RestTemplate ... @@ -142,7 +142,7 @@ The following example shows how to set up and use the `ExecutingResponseCreator` ---- val restTemplate = RestTemplate() - // Make sure to capture the request factory of the RestTemplate before binding + // Create ExecutingResponseCreator with the original request factory val withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory()) val mockServer = MockRestServiceServer.bindTo(restTemplate).build() @@ -156,16 +156,15 @@ The following example shows how to set up and use the `ExecutingResponseCreator` In the preceding example, we create the `ExecutingResponseCreator` using the `ClientHttpRequestFactory` from the `RestTemplate` _before_ `MockRestServiceServer` replaces -it with the custom one. -Then we define expectations with two kinds of response: +it with a different one that mocks responses. +Then we define expectations with two kinds of responses: * a stub `200` response for the `/profile` endpoint (no actual request will be executed) - * an "executing response" for the `/quoteOfTheDay` endpoint + * a response obtained through a call to the `/quoteOfTheDay` endpoint -In the second case, the request is executed by the `ClientHttpRequestFactory` that was +In the second case, the request is executed through the `ClientHttpRequestFactory` that was captured earlier. This generates a response that could e.g. come from an actual remote server, -depending on how the `RestTemplate` was originally configured, and MockMVC can be further -used to assert the content of the response. +depending on how the `RestTemplate` was originally configured. [[spring-mvc-test-client-static-imports]] == Static Imports diff --git a/spring-test/src/main/java/org/springframework/test/web/client/response/ExecutingResponseCreator.java b/spring-test/src/main/java/org/springframework/test/web/client/response/ExecutingResponseCreator.java index deb69a3b63c..6035f368f56 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/response/ExecutingResponseCreator.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/response/ExecutingResponseCreator.java @@ -27,15 +27,12 @@ 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. - *

Note that the input request is asserted to be a {@code MockClientHttpRequest} and - * the URI, method, headers and body are copied. - *

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): + * {@code ResponseCreator} that obtains the response by executing the request + * through a {@link ClientHttpRequestFactory}. This is useful in scenarios with + * multiple remote services where some need to be called rather than mocked. + *

The {@code ClientHttpRequestFactory} is typically obtained from the + * {@code RestTemplate} before it is passed to {@code MockRestServiceServer}, + * in effect using the original factory rather than the test factory: *


  * ResponseCreator withActualResponse = new ExecutingResponseCreator(restTemplate);
  * MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
@@ -53,7 +50,7 @@ public class ExecutingResponseCreator implements ResponseCreator {
 
 
 	/**
-	 * Create a {@code ExecutingResponseCreator} from a {@code ClientHttpRequestFactory}.
+	 * Create an instance with the given {@code ClientHttpRequestFactory}.
 	 * @param requestFactory the request factory to delegate to
 	 */
 	public ExecutingResponseCreator(ClientHttpRequestFactory requestFactory) {
@@ -63,7 +60,7 @@ public class ExecutingResponseCreator implements ResponseCreator {
 
 	@Override
 	public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException {
-		Assert.state(request instanceof MockClientHttpRequest, "Request should be an instance of MockClientHttpRequest");
+		Assert.state(request instanceof MockClientHttpRequest, "Expected a MockClientHttpRequest");
 		MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
 		ClientHttpRequest newRequest = this.requestFactory.createRequest(mockRequest.getURI(), mockRequest.getMethod());
 		newRequest.getHeaders().putAll(mockRequest.getHeaders());
diff --git a/spring-test/src/main/java/org/springframework/test/web/client/response/MockRestResponseCreators.java b/spring-test/src/main/java/org/springframework/test/web/client/response/MockRestResponseCreators.java
index 38ea2c1e3eb..75029904682 100644
--- a/spring-test/src/main/java/org/springframework/test/web/client/response/MockRestResponseCreators.java
+++ b/spring-test/src/main/java/org/springframework/test/web/client/response/MockRestResponseCreators.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * 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.
@@ -28,16 +28,11 @@ import org.springframework.lang.Nullable;
 import org.springframework.test.web.client.ResponseCreator;
 
 /**
- * Static factory methods for obtaining a {@link ResponseCreator} instance.
+ * Static factory methods to obtain a {@link ResponseCreator} with a fixed
+ * response.
  *
- * 

Eclipse users: consider adding this class as a Java editor - * favorite. To navigate, open the Preferences and type "favorites". - * - *

See also {@link ExecutingResponseCreator} for a {@code ResponseCreator} that is - * capable of performing an actual request. That case is not offered as a factory method - * here because of the early setup that is likely needed (capturing a request factory - * which wouldn't be available anymore when the factory methods are typically invoked, - * e.g. replaced in a {@code RestTemplate} by the {@code MockRestServiceServer}). + *

In addition, see also the {@link ExecutingResponseCreator} implementation + * that performs an actual requests to remote services. * * @author Rossen Stoyanchev * @since 3.2 diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java index c8f536c9aaf..92b95bc3d6e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -99,39 +99,34 @@ class MockRestServiceServerTests { @Test void executingResponseCreator() { - RestTemplate restTemplateWithMockEcho = createEchoRestTemplate(); + RestTemplate restTemplate = createEchoRestTemplate(); + ExecutingResponseCreator withActualCall = new ExecutingResponseCreator(restTemplate.getRequestFactory()); - final ExecutingResponseCreator withActualCall = new ExecutingResponseCreator(restTemplateWithMockEcho.getRequestFactory()); - MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplateWithMockEcho).build(); + MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build(); server.expect(requestTo("/profile")).andRespond(withSuccess()); server.expect(requestTo("/quoteOfTheDay")).andRespond(withActualCall); - var response1 = restTemplateWithMockEcho.getForEntity("/profile", String.class); - var response2 = restTemplateWithMockEcho.getForEntity("/quoteOfTheDay", String.class); + var response1 = restTemplate.getForEntity("/profile", String.class); + var response2 = restTemplate.getForEntity("/quoteOfTheDay", String.class); server.verify(); - assertThat(response1.getStatusCode().value()) - .as("response1 status").isEqualTo(200); - assertThat(response1.getBody()) - .as("response1 body").isNullOrEmpty(); - assertThat(response2.getStatusCode().value()) - .as("response2 status").isEqualTo(300); - assertThat(response2.getBody()) - .as("response2 body").isEqualTo("echo from /quoteOfTheDay"); + assertThat(response1.getStatusCode().value()).isEqualTo(200); + assertThat(response1.getBody()).isNullOrEmpty(); + assertThat(response2.getStatusCode().value()).isEqualTo(300); + assertThat(response2.getBody()).isEqualTo("echo from /quoteOfTheDay"); } private static RestTemplate createEchoRestTemplate() { - final ClientHttpRequestFactory echoRequestFactory = (uri, httpMethod) -> { - final MockClientHttpRequest req = new MockClientHttpRequest(httpMethod, uri); - String body = "echo from " + uri.getPath(); - final ClientHttpResponse resp = new MockClientHttpResponse(body.getBytes(StandardCharsets.UTF_8), - // Instead of 200, we use a less-common status code on purpose - HttpStatus.MULTIPLE_CHOICES); - resp.getHeaders().setContentType(MediaType.TEXT_PLAIN); - req.setResponse(resp); - return req; + ClientHttpRequestFactory requestFactory = (uri, httpMethod) -> { + MockClientHttpRequest request = new MockClientHttpRequest(httpMethod, uri); + ClientHttpResponse response = new MockClientHttpResponse( + ("echo from " + uri.getPath()).getBytes(StandardCharsets.UTF_8), + HttpStatus.MULTIPLE_CHOICES); // use a different status on purpose + response.getHeaders().setContentType(MediaType.TEXT_PLAIN); + request.setResponse(response); + return request; }; - return new RestTemplate(echoRequestFactory); + return new RestTemplate(requestFactory); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/web/client/response/ExecutingResponseCreatorTests.java b/spring-test/src/test/java/org/springframework/test/web/client/response/ExecutingResponseCreatorTests.java index 4c5efcc8975..63982612a37 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/response/ExecutingResponseCreatorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/response/ExecutingResponseCreatorTests.java @@ -17,17 +17,13 @@ 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; @@ -36,6 +32,7 @@ import org.springframework.mock.http.client.MockClientHttpResponse; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; /** * Tests for the {@link ExecutingResponseCreator} implementation. @@ -46,50 +43,29 @@ class ExecutingResponseCreatorTests { @Test void ensureRequestNotNull() { - final ExecutingResponseCreator responseCreator = new ExecutingResponseCreator((uri, method) -> null); + ExecutingResponseCreator responseCreator = new ExecutingResponseCreator((uri, method) -> null); assertThatIllegalStateException() .isThrownBy(() -> responseCreator.createResponse(null)) - .withMessage("Request should be an instance of MockClientHttpRequest"); + .withMessage("Expected a 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; - } - }; + ClientHttpRequest notAMockRequest = mock(ClientHttpRequest.class); assertThatIllegalStateException() .isThrownBy(() -> responseCreator.createResponse(notAMockRequest)) - .withMessage("Request should be an instance of MockClientHttpRequest"); + .withMessage("Expected a MockClientHttpRequest"); } @Test void requestIsCopied() throws IOException { - MockClientHttpRequest originalRequest = new MockClientHttpRequest(HttpMethod.POST, - "https://example.org"); - String body = "original body"; + MockClientHttpRequest originalRequest = new MockClientHttpRequest(HttpMethod.POST, "https://example.org"); originalRequest.getHeaders().add("X-example", "original"); - originalRequest.getBody().write(body.getBytes(StandardCharsets.UTF_8)); + originalRequest.getBody().write("original body".getBytes(StandardCharsets.UTF_8)); + MockClientHttpResponse originalResponse = new MockClientHttpResponse(new byte[0], 500); List factoryRequests = new ArrayList<>(); ClientHttpRequestFactory originalFactory = (uri, httpMethod) -> { @@ -99,8 +75,8 @@ class ExecutingResponseCreatorTests { return request; }; - final ExecutingResponseCreator responseCreator = new ExecutingResponseCreator(originalFactory); - final ClientHttpResponse response = responseCreator.createResponse(originalRequest); + ExecutingResponseCreator responseCreator = new ExecutingResponseCreator(originalFactory); + ClientHttpResponse response = responseCreator.createResponse(originalRequest); assertThat(response).as("response").isSameAs(originalResponse); assertThat(originalRequest.isExecuted()).as("originalRequest.isExecuted").isFalse(); @@ -109,16 +85,11 @@ class ExecutingResponseCreatorTests { .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()); + .satisfies(request -> { + assertThat(request).isNotSameAs(originalRequest); + assertThat(request.isExecuted()).isTrue(); + assertThat(request.getBody()).isNotSameAs(originalRequest.getBody()); + assertThat(request.getHeaders()).isNotSameAs(originalRequest.getHeaders()); }); } }