Skip to content

MockMvcClientHttpRequestFactory should implement AsyncClientHttpRequestFactory as well [SPR-15181] #19747

Closed
@spring-projects-issues

Description

@spring-projects-issues

Artem Bilan opened SPR-15181 and commented

The use-case is like:

AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(new MockMvcClientHttpRequestFactory(this.mockMvc));

new DirectFieldAccessor(this.serviceAsyncGatewayHandler)
		.setPropertyValue("asyncRestTemplate", asyncRestTemplate);

		this.mockMvc.perform(...);

To check the component based on the AsyncRestTemplate against some MVC environment.

But MockMvcClientHttpRequestFactory doesn't implement AsyncClientHttpRequestFactory and looks like there is no any other alternative unless home-cooked solution, for example:

private final class MockMvcAsyncClientHttpRequestFactory extends MockMvcClientHttpRequestFactory
		implements AsyncClientHttpRequestFactory {

	MockMvcAsyncClientHttpRequestFactory(MockMvc mockMvc) {
		super(mockMvc);
	}

	@Override
	public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException {
		final ClientHttpRequest syncDelegate = createRequest(uri, httpMethod);
		return new MockAsyncClientHttpRequest(httpMethod, uri) {

			@Override
			public HttpHeaders getHeaders() {
				return syncDelegate.getHeaders();
			}

			@Override
			public OutputStream getBody() throws IOException {
				return syncDelegate.getBody();
			}

			@Override
			protected ClientHttpResponse executeInternal() throws IOException {
				return syncDelegate.execute();
			}

		};
	}

}

Not sure that it will work for all use-case, but at least it meets my requirements even with the Basic Authentication header.

For out-of-the-box solution I suggest this modification to the MockMvcClientHttpRequestFactory:

public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {

		private final MockMvc mockMvc;


		public MockMvcClientHttpRequestFactory(MockMvc mockMvc) {
			Assert.notNull(mockMvc, "MockMvc must not be null");
			this.mockMvc = mockMvc;
		}


		@Override
		public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
			return new MockClientHttpRequest(httpMethod, uri) {

				@Override
				public ClientHttpResponse executeInternal() throws IOException {
					return doExecute(httpMethod, uri.toString(), getHeaders(), getBodyAsBytes());
				}

			};
		}

		@Override
		public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException {
			return new MockAsyncClientHttpRequest(httpMethod, uri) {

				@Override
				protected ClientHttpResponse executeInternal() throws IOException {
					return doExecute(httpMethod, uri.toString(), getHeaders(), getBodyAsBytes());
				}

			};

		}

		private ClientHttpResponse doExecute(HttpMethod httpMethod, String uri, HttpHeaders httpHeaders, byte[] body) {
			try {
				MockHttpServletRequestBuilder requestBuilder = request(httpMethod, uri);
				requestBuilder.content(body);
				requestBuilder.headers(httpHeaders);
				MvcResult mvcResult = MockMvcClientHttpRequestFactory.this.mockMvc.perform(requestBuilder).andReturn();
				MockHttpServletResponse servletResponse = mvcResult.getResponse();
				HttpStatus status = HttpStatus.valueOf(servletResponse.getStatus());
				byte[] reply = servletResponse.getContentAsByteArray();
				HttpHeaders headers = getResponseHeaders(servletResponse);
				MockClientHttpResponse clientResponse = new MockClientHttpResponse(reply, status);
				clientResponse.getHeaders().putAll(headers);
				return clientResponse;
			}
			catch (Exception ex) {
				byte[] reply = ex.toString().getBytes(StandardCharsets.UTF_8);
				return new MockClientHttpResponse(reply, HttpStatus.INTERNAL_SERVER_ERROR);
			}
		}

		private HttpHeaders getResponseHeaders(MockHttpServletResponse response) {
			HttpHeaders headers = new HttpHeaders();
			for (String name : response.getHeaderNames()) {
				List<String> values = response.getHeaders(name);
				for (String value : values) {
					headers.add(name, value);
				}
			}
			return headers;
		}

	}

Would be glad to see the fix in the 5.0, we have a PR in Spring Integration which now doesn't use MockMvc because of this limitation.

Thanks


Referenced from: commits 4da4f2b

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions