Refactor @ResponseBody and ResponseEntity tests

Introduce separate test classes for each base class in the hierarchy
above @ResponseBody and ResponseEntity result handlers.

Also start porting existing unit test cases for @ResponseBody and
ResponseEntity return value handlers.
This commit is contained in:
Rossen Stoyanchev 2016-06-24 10:15:06 -04:00
parent 3fe87ee225
commit cae8800183
7 changed files with 470 additions and 155 deletions

View File

@ -126,7 +126,7 @@ public abstract class ContentNegotiatingResultHandlerSupport implements Ordered
}
private List<MediaType> getAcceptableTypes(ServerWebExchange exchange) {
List<MediaType> mediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange);
List<MediaType> mediaTypes = getContentTypeResolver().resolveMediaTypes(exchange);
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
}

View File

@ -19,6 +19,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
@ -82,16 +83,22 @@ public abstract class AbstractMessageConverterResultHandler extends ContentNegot
publisher = Mono.empty();
}
elementType = bodyType.getGeneric(0);
if (Void.class.equals(elementType.getRawClass())) {
return Mono.from((Publisher<Void>) publisher);
}
}
else {
publisher = Mono.justOrEmpty(body);
elementType = bodyType;
}
if (Void.class.equals(elementType.getRawClass())) {
return Mono.from((Publisher<Void>) publisher);
}
List<MediaType> producibleTypes = getProducibleMediaTypes(elementType);
if (producibleTypes.isEmpty()) {
return Mono.error(new IllegalStateException(
"No converter for return value type: " + elementType));
}
MediaType bestMediaType = selectMediaType(exchange, producibleTypes);
if (bestMediaType != null) {

View File

@ -104,7 +104,7 @@ public class ResponseEntityResultHandler extends AbstractMessageConverterResultH
if (!entityHeaders.isEmpty()) {
entityHeaders.entrySet().stream()
.filter(entry -> responseHeaders.containsKey(entry.getKey()))
.filter(entry -> !responseHeaders.containsKey(entry.getKey()))
.forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue()));
}

View File

@ -0,0 +1,137 @@
/*
* Copyright 2002-2016 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
*
* http://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.reactive.result;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.web.reactive.accept.FixedContentTypeResolver;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.springframework.http.MediaType.ALL;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
import static org.springframework.http.MediaType.IMAGE_GIF;
import static org.springframework.http.MediaType.IMAGE_JPEG;
import static org.springframework.http.MediaType.IMAGE_PNG;
import static org.springframework.http.MediaType.TEXT_PLAIN;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
/**
* Unit tests for {@link ContentNegotiatingResultHandlerSupport}.
* @author Rossen Stoyanchev
*/
public class ContentNegotiatingResultHandlerSupportTests {
private TestHandlerSupport handlerSupport;
private MockServerHttpRequest request;
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.handlerSupport = new TestHandlerSupport();
this.request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path"));
this.exchange = new DefaultServerWebExchange(
this.request, new MockServerHttpResponse(), mock(WebSessionManager.class));
}
@Test
public void usesContentTypeResolver() throws Exception {
RequestedContentTypeResolver resolver = new FixedContentTypeResolver(IMAGE_GIF);
TestHandlerSupport handlerSupport = new TestHandlerSupport(resolver);
List<MediaType> mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG);
MediaType actual = handlerSupport.selectMediaType(this.exchange, mediaTypes);
assertEquals(IMAGE_GIF, actual);
}
@Test
public void producibleMediaTypesRequestAttribute() throws Exception {
Set<MediaType> producible = Collections.singleton(IMAGE_GIF);
this.exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producible);
List<MediaType> mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG);
MediaType actual = handlerSupport.selectMediaType(this.exchange, mediaTypes);
assertEquals(IMAGE_GIF, actual);
}
@Test // SPR-9160
public void sortsByQuality() throws Exception {
this.request.getHeaders().add("Accept", "text/plain; q=0.5, application/json");
List<MediaType> mediaTypes = Arrays.asList(TEXT_PLAIN, APPLICATION_JSON_UTF8);
MediaType actual = this.handlerSupport.selectMediaType(this.exchange, mediaTypes);
assertEquals(APPLICATION_JSON_UTF8, actual);
}
@Test
public void charsetFromAcceptHeader() throws Exception {
MediaType text8859 = MediaType.parseMediaType("text/plain;charset=ISO-8859-1");
MediaType textUtf8 = MediaType.parseMediaType("text/plain;charset=UTF-8");
this.request.getHeaders().setAccept(Collections.singletonList(text8859));
MediaType actual = this.handlerSupport.selectMediaType(this.exchange, Collections.singletonList(textUtf8));
assertEquals(text8859, actual);
}
@Test // SPR-12894
public void noConcreteMediaType() throws Exception {
List<MediaType> producible = Collections.singletonList(ALL);
MediaType actual = this.handlerSupport.selectMediaType(this.exchange, producible);
assertEquals(APPLICATION_OCTET_STREAM, actual);
}
private static class TestHandlerSupport extends ContentNegotiatingResultHandlerSupport {
protected TestHandlerSupport() {
this(new HeaderContentTypeResolver());
}
public TestHandlerSupport(RequestedContentTypeResolver contentTypeResolver) {
super(new GenericConversionService(), contentTypeResolver);
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright 2002-2016 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
*
* http://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.reactive.result.method.annotation;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.test.TestSubscriber;
import rx.Observable;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.support.ByteBufferDecoder;
import org.springframework.core.codec.support.ByteBufferEncoder;
import org.springframework.core.codec.support.JacksonJsonDecoder;
import org.springframework.core.codec.support.JacksonJsonEncoder;
import org.springframework.core.codec.support.Jaxb2Decoder;
import org.springframework.core.codec.support.Jaxb2Encoder;
import org.springframework.core.codec.support.JsonObjectDecoder;
import org.springframework.core.codec.support.StringDecoder;
import org.springframework.core.codec.support.StringEncoder;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
import org.springframework.http.converter.reactive.HttpMessageConverter;
import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link AbstractMessageConverterResultHandler}.
* @author Rossen Stoyanchev
*/
public class MessageConverterResultHandlerTests {
private AbstractMessageConverterResultHandler resultHandler;
private MockServerHttpResponse response = new MockServerHttpResponse();
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.resultHandler = createResultHandler();
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path"));
this.exchange = new DefaultServerWebExchange(request, this.response, mock(WebSessionManager.class));
}
@Test // SPR-12894
@Ignore // GH # 121
public void useDefaultContentType() throws Exception {
Object body = new ByteArrayResource("body".getBytes("UTF-8"));
ResolvableType bodyType = ResolvableType.forType(Resource.class);
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
assertEquals("image/jpeg", this.response.getHeaders().getFirst("Content-Type"));
TestSubscriber.subscribe(this.response.getBody())
.assertValuesWith(buf -> assertEquals("body",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
}
@Test
public void voidReturnType() throws Exception {
testVoidReturnType(null, ResolvableType.forType(Void.class));
testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class));
testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class));
testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class));
}
private void testVoidReturnType(Object body, ResolvableType bodyType) {
this.resultHandler.writeBody(this.exchange, body, bodyType).block(Duration.ofSeconds(5));
assertNull(this.response.getHeaders().get("Content-Type"));
assertNull(this.response.getBody());
}
@Test // SPR-13135
public void unsupportedReturnType() throws Exception {
ByteArrayOutputStream body = new ByteArrayOutputStream();
ResolvableType bodyType = ResolvableType.forType(OutputStream.class);
HttpMessageConverter<?> converter = new CodecHttpMessageConverter<>(new ByteBufferEncoder());
Mono<Void> mono = createResultHandler(converter).writeBody(this.exchange, body, bodyType);
TestSubscriber.subscribe(mono).assertError(IllegalStateException.class);
}
private AbstractMessageConverterResultHandler createResultHandler(HttpMessageConverter<?>... converters) {
List<HttpMessageConverter<?>> converterList;
if (ObjectUtils.isEmpty(converters)) {
converterList = new ArrayList<>();
converterList.add(new CodecHttpMessageConverter<>(new ByteBufferEncoder()));
converterList.add(new CodecHttpMessageConverter<>(new StringEncoder()));
converterList.add(new ResourceHttpMessageConverter());
converterList.add(new CodecHttpMessageConverter<>(new Jaxb2Encoder()));
converterList.add(new CodecHttpMessageConverter<>(new JacksonJsonEncoder()));
}
else {
converterList = Arrays.asList(converters);
}
GenericConversionService service = new GenericConversionService();
service.addConverter(new ReactiveStreamsToCompletableFutureConverter());
service.addConverter(new ReactiveStreamsToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new AbstractMessageConverterResultHandler(converterList, service, resolver) {};
}
}

View File

@ -17,143 +17,154 @@
package org.springframework.web.reactive.result.method.annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.support.ByteBufferEncoder;
import org.springframework.core.codec.support.JacksonJsonEncoder;
import org.springframework.core.codec.support.Jaxb2Encoder;
import org.springframework.core.codec.support.StringEncoder;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
import org.springframework.http.converter.reactive.HttpMessageConverter;
import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
import org.springframework.web.reactive.accept.FixedContentTypeResolver;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
/**
* Unit tests for {@link ResponseBodyResultHandler}.
*
* consider whether the logic under test is in a parent class, then see:
* <ul>
* <li>{@code MessageConverterResultHandlerTests},
* <li>{@code ContentNegotiatingResultHandlerSupportTests}
* </ul>
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class ResponseBodyResultHandlerTests {
private ResponseBodyResultHandler resultHandler;
private MockServerHttpResponse response = new MockServerHttpResponse();
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.resultHandler = createHandler();
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path"));
this.exchange = new DefaultServerWebExchange(request, this.response, mock(WebSessionManager.class));
}
private ResponseBodyResultHandler createHandler(HttpMessageConverter<?>... converters) {
List<HttpMessageConverter<?>> converterList;
if (ObjectUtils.isEmpty(converters)) {
converterList = new ArrayList<>();
converterList.add(new CodecHttpMessageConverter<>(new ByteBufferEncoder()));
converterList.add(new CodecHttpMessageConverter<>(new StringEncoder()));
converterList.add(new ResourceHttpMessageConverter());
converterList.add(new CodecHttpMessageConverter<>(new Jaxb2Encoder()));
converterList.add(new CodecHttpMessageConverter<>(new JacksonJsonEncoder()));
}
else {
converterList = Arrays.asList(converters);
}
GenericConversionService service = new GenericConversionService();
service.addConverter(new ReactiveStreamsToCompletableFutureConverter());
service.addConverter(new ReactiveStreamsToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseBodyResultHandler(converterList, new DefaultConversionService(), resolver);
}
@Test
public void supports() throws NoSuchMethodException {
ResponseBodyResultHandler handler = createHandler(new StringEncoder());
TestController controller = new TestController();
testSupports(controller, "handleReturningString", true);
testSupports(controller, "handleReturningVoid", true);
testSupports(controller, "doWork", false);
HandlerMethod hm = new HandlerMethod(controller, TestController.class.getMethod("notAnnotated"));
TestRestController restController = new TestRestController();
testSupports(restController, "handleReturningString", true);
testSupports(restController, "handleReturningVoid", true);
}
private void testSupports(Object controller, String method, boolean result) throws NoSuchMethodException {
HandlerMethod hm = handlerMethod(controller, method);
ResolvableType type = ResolvableType.forMethodParameter(hm.getReturnType());
assertFalse(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherString"));
type = ResolvableType.forMethodParameter(hm.getReturnType());
assertTrue(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
hm = new HandlerMethod(controller, TestController.class.getMethod("publisherVoid"));
type = ResolvableType.forMethodParameter(hm.getReturnType());
assertTrue(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
HandlerResult handlerResult = new HandlerResult(hm, null, type, new ExtendedModelMap());
assertEquals(result, this.resultHandler.supports(handlerResult));
}
@Test
public void defaultOrder() throws Exception {
ResponseBodyResultHandler handler = createHandler(new StringEncoder());
assertEquals(100, handler.getOrder());
}
@Test
public void usesContentTypeResolver() throws Exception {
RequestedContentTypeResolver resolver = new FixedContentTypeResolver(APPLICATION_JSON_UTF8);
HandlerResultHandler handler = createHandler(resolver, new StringEncoder(), new JacksonJsonEncoder());
ServerWebExchange exchange = createExchange("/foo");
HandlerResult result = new HandlerResult(new Object(), "fooValue", ResolvableType.forClass(String.class));
handler.handleResult(exchange, result).block();
assertEquals(APPLICATION_JSON_UTF8, exchange.getResponse().getHeaders().getContentType());
}
@Test
public void detectsProducibleMediaTypesAttribute() throws Exception {
ServerWebExchange exchange = createExchange("/foo");
Set<MediaType> mediaTypes = Collections.singleton(MediaType.APPLICATION_JSON);
exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
HandlerResultHandler handler = createHandler(new StringEncoder(), new JacksonJsonEncoder());
HandlerResult result = new HandlerResult(new Object(), "fooValue", ResolvableType.forClass(String.class));
handler.handleResult(exchange, result).block();
assertEquals(MediaType.APPLICATION_JSON, exchange.getResponse().getHeaders().getContentType());
assertEquals(100, this.resultHandler.getOrder());
}
private ResponseBodyResultHandler createHandler(Encoder<?>... encoders) {
return createHandler(new HeaderContentTypeResolver(), encoders);
}
private ResponseBodyResultHandler createHandler(RequestedContentTypeResolver resolver,
Encoder<?>... encoders) {
List<HttpMessageConverter<?>> converters = Arrays.stream(encoders)
.map(encoder -> new CodecHttpMessageConverter<>(encoder, null))
.collect(Collectors.toList());
return new ResponseBodyResultHandler(converters, new DefaultConversionService(), resolver);
}
private ServerWebExchange createExchange(String path) throws URISyntaxException {
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path));
WebSessionManager sessionManager = mock(WebSessionManager.class);
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
private HandlerMethod handlerMethod(Object controller, String method) throws NoSuchMethodException {
return new HandlerMethod(controller, controller.getClass().getMethod(method));
}
@SuppressWarnings("unused")
@RestController @SuppressWarnings("unused")
private static class TestRestController {
public String handleReturningString() {
return null;
}
public Void handleReturningVoid() {
return null;
}
}
@Controller @SuppressWarnings("unused")
private static class TestController {
public Publisher<String> notAnnotated() {
@ResponseBody
public String handleReturningString() {
return null;
}
@ResponseBody
public Publisher<String> publisherString() {
public Void handleReturningVoid() {
return null;
}
@ResponseBody
public Publisher<Void> publisherVoid() {
public String doWork() {
return null;
}
}
}

View File

@ -16,134 +16,139 @@
package org.springframework.web.reactive.result.method.annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import reactor.core.test.TestSubscriber;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.support.ByteBufferEncoder;
import org.springframework.core.codec.support.JacksonJsonEncoder;
import org.springframework.core.codec.support.Jaxb2Encoder;
import org.springframework.core.codec.support.StringEncoder;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.ReactiveStreamsToCompletableFutureConverter;
import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
import org.springframework.http.converter.reactive.HttpMessageConverter;
import org.springframework.http.converter.reactive.ResourceHttpMessageConverter;
import org.springframework.http.server.reactive.MockServerHttpRequest;
import org.springframework.http.server.reactive.MockServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.method.HandlerMethod;
import org.springframework.ui.ModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
import org.springframework.web.reactive.accept.FixedContentTypeResolver;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
/**
* Unit tests for {@link ResponseEntityResultHandler}.
* Unit tests for {@link ResponseEntityResultHandler}. When adding a test also
* consider whether the logic under test is in a parent class, then see:
* <ul>
* <li>{@code MessageConverterResultHandlerTests},
* <li>{@code ContentNegotiatingResultHandlerSupportTests}
* </ul>
* @author Rossen Stoyanchev
*/
public class ResponseEntityResultHandlerTests {
private static final Object HANDLER = new Object();
private ResponseEntityResultHandler resultHandler;
private MockServerHttpResponse response = new MockServerHttpResponse();
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.resultHandler = createHandler();
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path"));
this.exchange = new DefaultServerWebExchange(request, this.response, mock(WebSessionManager.class));
}
private ResponseEntityResultHandler createHandler(HttpMessageConverter<?>... converters) {
List<HttpMessageConverter<?>> converterList;
if (ObjectUtils.isEmpty(converters)) {
converterList = new ArrayList<>();
converterList.add(new CodecHttpMessageConverter<>(new ByteBufferEncoder()));
converterList.add(new CodecHttpMessageConverter<>(new StringEncoder()));
converterList.add(new ResourceHttpMessageConverter());
converterList.add(new CodecHttpMessageConverter<>(new Jaxb2Encoder()));
converterList.add(new CodecHttpMessageConverter<>(new JacksonJsonEncoder()));
}
else {
converterList = Arrays.asList(converters);
}
GenericConversionService service = new GenericConversionService();
service.addConverter(new ReactiveStreamsToCompletableFutureConverter());
service.addConverter(new ReactiveStreamsToRxJava1Converter());
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseEntityResultHandler(converterList, new DefaultConversionService(), resolver);
}
@Test
public void supports() throws NoSuchMethodException {
ResponseEntityResultHandler handler = createHandler(new StringEncoder());
TestController controller = new TestController();
ModelMap model = new ExtendedModelMap();
HandlerMethod hm = new HandlerMethod(controller, TestController.class.getMethod("responseString"));
ResolvableType type = ResolvableType.forMethodParameter(hm.getReturnType());
assertTrue(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
ResolvableType type = ResolvableType.forClassWithGenerics(ResponseEntity.class, String.class);
assertTrue(this.resultHandler.supports(new HandlerResult(HANDLER, null, type, model)));
hm = new HandlerMethod(controller, TestController.class.getMethod("responseVoid"));
type = ResolvableType.forMethodParameter(hm.getReturnType());
assertTrue(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
type = ResolvableType.forClassWithGenerics(ResponseEntity.class, Void.class);
assertTrue(this.resultHandler.supports(new HandlerResult(HANDLER, null, type, model)));
hm = new HandlerMethod(controller, TestController.class.getMethod("string"));
type = ResolvableType.forMethodParameter(hm.getReturnType());
assertFalse(handler.supports(new HandlerResult(hm, null, type, new ExtendedModelMap())));
type = ResolvableType.forClass(Void.class);
assertFalse(this.resultHandler.supports(new HandlerResult(HANDLER, null, type, model)));
}
@Test
public void defaultOrder() throws Exception {
ResponseEntityResultHandler handler = createHandler(new StringEncoder());
assertEquals(0, handler.getOrder());
assertEquals(0, this.resultHandler.getOrder());
}
@Test
public void jsonResponseBody() throws Exception {
RequestedContentTypeResolver resolver = new FixedContentTypeResolver(APPLICATION_JSON_UTF8);
HandlerResultHandler handler = createHandler(resolver, new StringEncoder(), new JacksonJsonEncoder());
public void statusCode() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(ResponseEntity.class, Void.class);
HandlerResult result = new HandlerResult(HANDLER, ResponseEntity.noContent().build(), type);
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
TestController controller = new TestController();
HandlerMethod hm = new HandlerMethod(controller, controller.getClass().getMethod("responseString"));
ResolvableType type = ResolvableType.forMethodParameter(hm.getReturnType());
HandlerResult result = new HandlerResult(hm, ResponseEntity.ok("fooValue"), type);
ServerWebExchange exchange = createExchange("/foo");
handler.handleResult(exchange, result).block();
assertEquals(HttpStatus.OK, this.response.getStatus());
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
TestSubscriber.subscribe(this.response.getBody())
.assertValuesWith(buf -> assertEquals("\"fooValue\"",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
assertEquals(HttpStatus.NO_CONTENT, this.response.getStatus());
assertEquals(0, this.response.getHeaders().size());
assertNull(this.response.getBody());
}
@Test
public void headers() throws Exception {
URI location = new URI("/path");
ResolvableType type = ResolvableType.forClassWithGenerics(ResponseEntity.class, Void.class);
HandlerResult result = new HandlerResult(HANDLER, ResponseEntity.created(location).build(), type);
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
private ResponseEntityResultHandler createHandler(Encoder<?>... encoders) {
return createHandler(new HeaderContentTypeResolver(), encoders);
}
private ResponseEntityResultHandler createHandler(RequestedContentTypeResolver resolver,
Encoder<?>... encoders) {
List<HttpMessageConverter<?>> converters = Arrays.stream(encoders)
.map(encoder -> new CodecHttpMessageConverter<>(encoder, null))
.collect(Collectors.toList());
return new ResponseEntityResultHandler(converters, new DefaultConversionService(), resolver);
}
private ServerWebExchange createExchange(String path) throws URISyntaxException {
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path));
WebSessionManager sessionManager = mock(WebSessionManager.class);
return new DefaultServerWebExchange(request, this.response, sessionManager);
}
@SuppressWarnings("unused")
private static class TestController {
public ResponseEntity<String> responseString() {
return null;
}
public ResponseEntity<Void> responseVoid() {
return null;
}
public String string() {
return null;
}
assertEquals(HttpStatus.CREATED, this.response.getStatus());
assertEquals(1, this.response.getHeaders().size());
assertEquals(location, this.response.getHeaders().getLocation());
assertNull(this.response.getBody());
}
}