From f9dcd428aecd9e1bc44af4737b459c734c40ee42 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 12 Sep 2022 11:36:26 +0200 Subject: [PATCH] Extract Mock HTTP client requests and responses This commit extracts Mock HTTP client request and response for the imperative variant. These are made available in the testFixtures configuration for shared usage. --- ...rceptingClientHttpRequestFactoryTests.java | 133 +++------------ .../http/client/MockClientHttpRequest.java | 154 ++++++++++++++++++ .../http/client/MockClientHttpResponse.java | 99 +++++++++++ .../testfixture/http/client/package-info.java | 9 + .../http/client/reactive/package-info.java | 9 + 5 files changed, 290 insertions(+), 114 deletions(-) create mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java create mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java create mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/package-info.java create mode 100644 spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/package-info.java diff --git a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java index 657a9ea3487..a41cdcea516 100644 --- a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java @@ -16,23 +16,21 @@ package org.springframework.http.client; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; -import org.springframework.http.HttpStatus; import org.springframework.http.client.support.HttpRequestWrapper; +import org.springframework.web.testfixture.http.client.MockClientHttpRequest; +import org.springframework.web.testfixture.http.client.MockClientHttpResponse; import static org.assertj.core.api.Assertions.assertThat; @@ -44,12 +42,16 @@ class InterceptingClientHttpRequestFactoryTests { private RequestFactoryMock requestFactoryMock = new RequestFactoryMock(); - private RequestMock requestMock = new RequestMock(); + private MockClientHttpRequest requestMock = new MockClientHttpRequest(); - private ResponseMock responseMock = new ResponseMock(); + private MockClientHttpResponse responseMock = new MockClientHttpResponse(); private InterceptingClientHttpRequestFactory requestFactory; + @BeforeEach + void beforeEach() { + this.requestMock.setResponse(this.responseMock); + } @Test void basic() throws Exception { @@ -65,7 +67,7 @@ class InterceptingClientHttpRequestFactoryTests { assertThat(((NoOpInterceptor) interceptors.get(0)).invoked).isTrue(); assertThat(((NoOpInterceptor) interceptors.get(1)).invoked).isTrue(); assertThat(((NoOpInterceptor) interceptors.get(2)).invoked).isTrue(); - assertThat(requestMock.executed).isTrue(); + assertThat(requestMock.isExecuted()).isTrue(); assertThat(response).isSameAs(responseMock); } @@ -81,7 +83,7 @@ class InterceptingClientHttpRequestFactoryTests { ClientHttpResponse response = request.execute(); assertThat(((NoOpInterceptor) interceptors.get(1)).invoked).isFalse(); - assertThat(requestMock.executed).isFalse(); + assertThat(requestMock.isExecuted()).isFalse(); assertThat(response).isSameAs(responseMock); } @@ -92,19 +94,19 @@ class InterceptingClientHttpRequestFactoryTests { final String otherValue = "Baz"; ClientHttpRequestInterceptor interceptor = (request, body, execution) -> { - HttpRequestWrapper wrapper = new HttpRequestWrapper(request); - wrapper.getHeaders().add(headerName, otherValue); - return execution.execute(wrapper, body); - }; + HttpRequestWrapper wrapper = new HttpRequestWrapper(request); + wrapper.getHeaders().add(headerName, otherValue); + return execution.execute(wrapper, body); + }; - requestMock = new RequestMock() { + requestMock = new MockClientHttpRequest() { @Override - public ClientHttpResponse execute() throws IOException { + protected ClientHttpResponse executeInternal() throws IOException { List headerValues = getHeaders().get(headerName); assertThat(headerValues.size()).isEqualTo(2); assertThat(headerValues.get(0)).isEqualTo(headerValue); assertThat(headerValues.get(1)).isEqualTo(otherValue); - return super.execute(); + return responseMock; } }; requestMock.getHeaders().add(headerName, headerValue); @@ -177,7 +179,7 @@ class InterceptingClientHttpRequestFactoryTests { ClientHttpRequest request = requestFactory.createRequest(new URI("https://example.com"), HttpMethod.GET); request.execute(); - assertThat(Arrays.equals(changedBody, requestMock.body.toByteArray())).isTrue(); + assertThat(Arrays.equals(changedBody, requestMock.getBodyAsBytes())).isTrue(); } @@ -205,101 +207,4 @@ class InterceptingClientHttpRequestFactoryTests { } - - private class RequestMock implements ClientHttpRequest { - - private URI uri; - - private HttpMethod method; - - private HttpHeaders headers = new HttpHeaders(); - - private ByteArrayOutputStream body = new ByteArrayOutputStream(); - - private boolean executed = false; - - private RequestMock() { - } - - @Override - public URI getURI() { - return uri; - } - - public void setURI(URI uri) { - this.uri = uri; - } - - @Override - public HttpMethod getMethod() { - return method; - } - - @Override - @Deprecated - public String getMethodValue() { - return method.name(); - } - - public void setMethod(HttpMethod method) { - this.method = method; - } - - @Override - public HttpHeaders getHeaders() { - return headers; - } - - @Override - public OutputStream getBody() throws IOException { - return body; - } - - @Override - public ClientHttpResponse execute() throws IOException { - executed = true; - return responseMock; - } - } - - - private static class ResponseMock implements ClientHttpResponse { - - private HttpStatus statusCode = HttpStatus.OK; - - private String statusText = ""; - - private HttpHeaders headers = new HttpHeaders(); - - @Override - public HttpStatus getStatusCode() throws IOException { - return statusCode; - } - - @Override - @SuppressWarnings("deprecation") - public int getRawStatusCode() throws IOException { - return statusCode.value(); - } - - @Override - public String getStatusText() throws IOException { - return statusText; - } - - @Override - public HttpHeaders getHeaders() { - return headers; - } - - @Override - public InputStream getBody() throws IOException { - return null; - } - - @Override - public void close() { - } - } - } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java new file mode 100644 index 00000000000..169b9e7ca48 --- /dev/null +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2002-2022 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.web.testfixture.http.client; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StreamUtils; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Mock implementation of {@link ClientHttpRequest}. + * + * @author Brian Clozel + * @author Rossen Stoyanchev + */ +public class MockClientHttpRequest implements ClientHttpRequest { + + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private final HttpHeaders headers = new HttpHeaders(); + + private HttpMethod httpMethod; + + private URI uri; + + private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); + + @Nullable + private ClientHttpResponse clientHttpResponse; + + private boolean executed = false; + + + public MockClientHttpRequest() { + this.httpMethod = HttpMethod.GET; + try { + this.uri = new URI("/"); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + public MockClientHttpRequest(HttpMethod httpMethod, String urlTemplate, Object... vars) { + this.httpMethod = httpMethod; + this.uri = UriComponentsBuilder.fromUriString(urlTemplate).buildAndExpand(vars).encode().toUri(); + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public OutputStream getBody() throws IOException { + return this.body; + } + + public byte[] getBodyAsBytes() { + return this.body.toByteArray(); + } + + public String getBodyAsString() { + return getBodyAsString(DEFAULT_CHARSET); + } + + public String getBodyAsString(Charset charset) { + return StreamUtils.copyToString(this.body, charset); + } + + public void setMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + @Override + public HttpMethod getMethod() { + return this.httpMethod; + } + + @Override + @Deprecated + public String getMethodValue() { + return this.httpMethod.name(); + } + + public void setURI(URI uri) { + this.uri = uri; + } + + @Override + public URI getURI() { + return this.uri; + } + + public void setResponse(ClientHttpResponse clientHttpResponse) { + this.clientHttpResponse = clientHttpResponse; + } + + public boolean isExecuted() { + return this.executed; + } + + @Override + public final ClientHttpResponse execute() throws IOException { + this.executed = true; + return executeInternal(); + } + + protected ClientHttpResponse executeInternal() throws IOException { + Assert.state(this.clientHttpResponse != null, "No ClientHttpResponse"); + return this.clientHttpResponse; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.httpMethod); + sb.append(' ').append(this.uri); + if (!getHeaders().isEmpty()) { + sb.append(", headers: ").append(getHeaders()); + } + if (sb.length() == 0) { + sb.append("Not yet initialized"); + } + return sb.toString(); + } + +} diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java new file mode 100644 index 00000000000..ba8ed077a06 --- /dev/null +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpResponse.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2022 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.web.testfixture.http.client; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; + +/** + * Mock implementation of {@link ClientHttpResponse}. + * + * @author Rossen Stoyanchev + * @author Brian Clozel + */ +public class MockClientHttpResponse implements ClientHttpResponse { + + private final HttpHeaders headers = new HttpHeaders(); + + private final HttpStatus status; + + private InputStream body; + + + public MockClientHttpResponse() { + this.status = HttpStatus.OK; + } + + public MockClientHttpResponse(HttpStatus statusCode) { + Assert.notNull(statusCode, "HttpStatus is required"); + this.status = statusCode; + } + + @Override + public HttpStatus getStatusCode() throws IOException { + return this.status; + } + + @Override + @SuppressWarnings("deprecation") + public int getRawStatusCode() throws IOException { + return this.status.value(); + } + + @Override + public String getStatusText() throws IOException { + return this.status.getReasonPhrase(); + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public InputStream getBody() throws IOException { + return this.body; + } + + public void setBody(byte[] body) { + Assert.notNull(body, "body is required"); + this.body = new ByteArrayInputStream(body); + } + + public void setBody(String body) { + Assert.notNull(body, "body is required"); + this.body = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void close() { + try { + getBody().close(); + } + catch (IOException ex) { + // ignore + } + } + +} diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/package-info.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/package-info.java new file mode 100644 index 00000000000..d153421f5c9 --- /dev/null +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/package-info.java @@ -0,0 +1,9 @@ +/** + * Contains mock request and response types for the imperative HTTP client + */ +@NonNullApi +@NonNullFields +package org.springframework.web.testfixture.http.client; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/package-info.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/package-info.java new file mode 100644 index 00000000000..68dbdb58321 --- /dev/null +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/package-info.java @@ -0,0 +1,9 @@ +/** + * Contains mock request and response types for the reactive HTTP client + */ +@NonNullApi +@NonNullFields +package org.springframework.web.testfixture.http.client.reactive; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields;