Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-15181

MockMvcClientHttpRequestFactory should implement AsyncClientHttpRequestFactory as well

    Details

    • Type: Improvement
    • Status: Closed
    • Priority: Minor
    • Resolution: Complete
    • Affects Version/s: None
    • Fix Version/s: 5.0 RC1
    • Component/s: Test
    • Labels:
      None
    • Last commented by a User:
      true

      Description

      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

        Attachments

          Activity

            People

            • Assignee:
              rstoya05-aop Rossen Stoyanchev
              Reporter:
              abilan Artem Bilan
              Last updater:
              St├ęphane Nicoll
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                1 year, 25 weeks, 2 days ago