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;