diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupportTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupportTests.java index 211706577b462119849eff9eaa575557fccd8fd4..08bc361585dba295731e64e1d7639436448e8a9e 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupportTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ContentNegotiatingResultHandlerSupportTests.java @@ -52,7 +52,7 @@ import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_T */ public class ContentNegotiatingResultHandlerSupportTests { - private TestHandlerSupport handlerSupport; + private TestResultHandler resultHandler; private MockServerHttpRequest request; @@ -61,7 +61,7 @@ public class ContentNegotiatingResultHandlerSupportTests { @Before public void setUp() throws Exception { - this.handlerSupport = new TestHandlerSupport(); + this.resultHandler = new TestResultHandler(); this.request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path")); this.exchange = new DefaultServerWebExchange( this.request, new MockServerHttpResponse(), new MockWebSessionManager()); @@ -70,11 +70,9 @@ public class ContentNegotiatingResultHandlerSupportTests { @Test public void usesContentTypeResolver() throws Exception { - RequestedContentTypeResolver resolver = new FixedContentTypeResolver(IMAGE_GIF); - TestHandlerSupport handlerSupport = new TestHandlerSupport(resolver); - + TestResultHandler resultHandler = new TestResultHandler(new FixedContentTypeResolver(IMAGE_GIF)); List mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG); - MediaType actual = handlerSupport.selectMediaType(this.exchange, mediaTypes); + MediaType actual = resultHandler.selectMediaType(this.exchange, mediaTypes); assertEquals(IMAGE_GIF, actual); } @@ -85,7 +83,7 @@ public class ContentNegotiatingResultHandlerSupportTests { this.exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producible); List mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG); - MediaType actual = handlerSupport.selectMediaType(this.exchange, mediaTypes); + MediaType actual = resultHandler.selectMediaType(this.exchange, mediaTypes); assertEquals(IMAGE_GIF, actual); } @@ -95,7 +93,7 @@ public class ContentNegotiatingResultHandlerSupportTests { this.request.getHeaders().add("Accept", "text/plain; q=0.5, application/json"); List mediaTypes = Arrays.asList(TEXT_PLAIN, APPLICATION_JSON_UTF8); - MediaType actual = this.handlerSupport.selectMediaType(this.exchange, mediaTypes); + MediaType actual = this.resultHandler.selectMediaType(this.exchange, mediaTypes); assertEquals(APPLICATION_JSON_UTF8, actual); } @@ -104,9 +102,8 @@ public class ContentNegotiatingResultHandlerSupportTests { 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)); + MediaType actual = this.resultHandler.selectMediaType(this.exchange, Collections.singletonList(textUtf8)); assertEquals(text8859, actual); } @@ -114,21 +111,20 @@ public class ContentNegotiatingResultHandlerSupportTests { @Test // SPR-12894 public void noConcreteMediaType() throws Exception { List producible = Collections.singletonList(ALL); - MediaType actual = this.handlerSupport.selectMediaType(this.exchange, producible); + MediaType actual = this.resultHandler.selectMediaType(this.exchange, producible); assertEquals(APPLICATION_OCTET_STREAM, actual); } + @SuppressWarnings("WeakerAccess") + private static class TestResultHandler extends ContentNegotiatingResultHandlerSupport { - - private static class TestHandlerSupport extends ContentNegotiatingResultHandlerSupport { - - protected TestHandlerSupport() { + protected TestResultHandler() { this(new HeaderContentTypeResolver()); } - public TestHandlerSupport(RequestedContentTypeResolver contentTypeResolver) { + public TestResultHandler(RequestedContentTypeResolver contentTypeResolver) { super(new GenericConversionService(), contentTypeResolver); } } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java index 2a83857cf19fa984ae5f1563be6f33a95b90eb64..f415b020792dfb6e62a2753888ce38dba704c6f2 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java @@ -21,11 +21,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import org.bouncycastle.util.Arrays; + import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** @@ -66,6 +69,8 @@ public class ResolvableMethod { private String methodName; + private Class[] argumentTypes; + private ResolvableType returnType; private final List> annotationTypes = new ArrayList<>(4); @@ -81,6 +86,11 @@ public class ResolvableMethod { return this; } + public ResolvableMethod argumentTypes(Class... argumentTypes) { + this.argumentTypes = argumentTypes; + return this; + } + public ResolvableMethod returning(ResolvableType resolvableType) { this.returnType = resolvableType; return this; @@ -94,16 +104,21 @@ public class ResolvableMethod { public Method resolve() { // String comparison (ResolvableType's with different providers) - String expected = this.returnType != null ? this.returnType.toString() : null; + String expectedReturnType = getReturnType(); Set methods = MethodIntrospector.selectMethods(this.targetClass, (ReflectionUtils.MethodFilter) method -> { - String actual = ResolvableType.forMethodReturnType(method).toString(); if (this.methodName != null && !this.methodName.equals(method.getName())) { return false; } - if (expected != null) { - if (!actual.equals(expected) && !Object.class.equals(method.getDeclaringClass())) { + if (getReturnType() != null) { + String actual = ResolvableType.forMethodReturnType(method).toString(); + if (!actual.equals(getReturnType()) && !Object.class.equals(method.getDeclaringClass())) { + return false; + } + } + if (!ObjectUtils.isEmpty(this.argumentTypes)) { + if (!Arrays.areEqual(this.argumentTypes, method.getParameterTypes())) { return false; } } @@ -121,6 +136,10 @@ public class ResolvableMethod { return methods.iterator().next(); } + private String getReturnType() { + return this.returnType != null ? this.returnType.toString() : null; + } + public MethodParameter resolveReturnType() { Method method = resolve(); return new MethodParameter(method, -1); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java index 23608b0124687d4c0ae866c08e4497b0babcc578..cb2165e400d9fb2d82bfd4920e50687f0e7cf5a2 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/HandlerMethodMappingTests.java @@ -49,7 +49,6 @@ import static org.junit.Assert.assertNull; /** * Unit tests for {@link AbstractHandlerMethodMapping}. - * * @author Rossen Stoyanchev */ public class HandlerMethodMappingTests { @@ -82,8 +81,8 @@ public class HandlerMethodMappingTests { public void directMatch() throws Exception { String key = "foo"; this.mapping.registerMapping(key, this.handler, this.method1); - Mono result = this.mapping.getHandler(createExchange(HttpMethod.GET, key)); + assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod()); } @@ -102,9 +101,7 @@ public class HandlerMethodMappingTests { this.mapping.registerMapping("/fo?", this.handler, this.method2); Mono result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo")); - TestSubscriber - .subscribe(result) - .assertError(IllegalStateException.class); + TestSubscriber.subscribe(result).assertError(IllegalStateException.class); } @Test @@ -115,6 +112,7 @@ public class HandlerMethodMappingTests { this.mapping.registerMapping(key2, this.handler, this.method2); List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1); + assertNotNull(directUrlMatches); assertEquals(1, directUrlMatches.size()); assertEquals(key1, directUrlMatches.get(0)); @@ -130,6 +128,7 @@ public class HandlerMethodMappingTests { this.mapping.registerMapping(key2, handler2, this.method1); List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1); + assertNotNull(directUrlMatches); assertEquals(1, directUrlMatches.size()); assertEquals(key1, directUrlMatches.get(0)); @@ -140,14 +139,17 @@ public class HandlerMethodMappingTests { String key = "foo"; this.mapping.registerMapping(key, this.handler, this.method1); Mono result = this.mapping.getHandler(createExchange(HttpMethod.GET, key)); + assertNotNull(result.block()); this.mapping.unregisterMapping(key); result = this.mapping.getHandler(createExchange(HttpMethod.GET, key)); + assertNull(result.block()); assertNull(this.mapping.getMappingRegistry().getMappingsByUrl(key)); } + private ServerWebExchange createExchange(HttpMethod httpMethod, String path) throws URISyntaxException { ServerHttpRequest request = new MockServerHttpRequest(httpMethod, new URI(path)); WebSessionManager sessionManager = new MockWebSessionManager(); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java index 9a5b5c26edd9b8bf17eade419c133dd8661c86da..c2944ea5de11204667d21c84b391a11b29ffc946 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java @@ -17,197 +17,156 @@ package org.springframework.web.reactive.result.method; import java.lang.reflect.Method; import java.net.URI; -import java.util.Arrays; import java.util.Collections; +import java.util.Optional; import org.junit.Before; import org.junit.Test; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.Signal; -import reactor.core.util.SignalKind; +import reactor.core.test.TestSubscriber; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.MockServerHttpRequest; +import org.springframework.http.server.reactive.MockServerHttpResponse; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.ModelMap; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.HandlerResult; -import org.springframework.web.reactive.result.method.annotation.RequestParamMethodArgumentResolver; +import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; -import org.springframework.web.server.session.WebSessionManager; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** + * Unit tests for {@link InvocableHandlerMethod}. * @author Rossen Stoyanchev */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public class InvocableHandlerMethodTests { - private ServerHttpRequest request; - private ServerWebExchange exchange; - private ModelMap model; + private ModelMap model = new ExtendedModelMap(); @Before public void setUp() throws Exception { - WebSessionManager sessionManager = new MockWebSessionManager(); - this.request = mock(ServerHttpRequest.class); - this.exchange = new DefaultServerWebExchange(request, mock(ServerHttpResponse.class), sessionManager); - this.model = new ExtendedModelMap(); + this.exchange = new DefaultServerWebExchange( + new MockServerHttpRequest(HttpMethod.GET, new URI("http://localhost:8080/path")), + new MockServerHttpResponse(), + new MockWebSessionManager()); } @Test - public void noArgsMethod() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("noArgs"); - + public void invokeMethodWithNoArguments() throws Exception { + InvocableHandlerMethod hm = handlerMethod("noArgs"); Mono mono = hm.invokeForRequest(this.exchange, this.model); - HandlerResult value = mono.block(); - assertNotNull(value); - assertEquals("success", value.getReturnValue().get()); + assertHandlerResultValue(mono, "success"); } @Test - public void resolveArgToZeroValues() throws Exception { - when(this.request.getURI()).thenReturn(new URI("http://localhost:8080/path")); - when(this.request.getQueryParams()).thenReturn(new LinkedMultiValueMap<>()); - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); - hm.setHandlerMethodArgumentResolvers(Collections.singletonList( - new RequestParamMethodArgumentResolver(new GenericConversionService(), null, false))); - + public void invokeMethodWithNoValue() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg"); + addResolver(hm, Mono.empty()); Mono mono = hm.invokeForRequest(this.exchange, this.model); - HandlerResult value = mono.block(); - assertNotNull(value); - assertEquals("success:null", value.getReturnValue().get()); + assertHandlerResultValue(mono, "success:null"); } @Test - public void resolveArgToOneValue() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); + public void invokeMethodWithValue() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg", String.class); addResolver(hm, Mono.just("value1")); - Mono mono = hm.invokeForRequest(this.exchange, this.model); - HandlerResult value = mono.block(); - assertNotNull(value); - assertEquals("success:value1", value.getReturnValue().get()); + assertHandlerResultValue(mono, "success:value1"); } @Test - public void resolveArgToMultipleValues() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); - addResolver(hm, Flux.fromIterable(Arrays.asList("value1", "value2", "value3"))); - + public void noMatchingResolver() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg", String.class); Mono mono = hm.invokeForRequest(this.exchange, this.model); - HandlerResult value = mono.block(); - - assertNotNull(value); - assertEquals("success:value1", value.getReturnValue().get()); - } - - @Test - public void noResolverForArg() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); - Publisher publisher = hm.invokeForRequest(this.exchange, this.model); - Throwable ex = awaitErrorSignal(publisher); - - assertEquals(IllegalStateException.class, ex.getClass()); - assertEquals("No resolver for argument [0] of type [java.lang.String] on method " + - "[" + hm.getMethod().toGenericString() + "]", ex.getMessage()); + TestSubscriber.subscribe(mono) + .assertError(IllegalStateException.class) + .assertErrorMessage("No resolver for argument [0] of type [java.lang.String] " + + "on method [" + hm.getMethod().toGenericString() + "]"); } @Test - public void resolveArgumentWithThrownException() throws Exception { - HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class); - when(resolver.supportsParameter(any())).thenReturn(true); - when(resolver.resolveArgument(any(), any(), any())).thenThrow(new IllegalStateException("boo")); - - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); - hm.setHandlerMethodArgumentResolvers(Collections.singletonList(resolver)); - - Publisher publisher = hm.invokeForRequest(this.exchange, this.model); - Throwable ex = awaitErrorSignal(publisher); + public void resolverThrowsException() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg", String.class); + addResolver(hm, Mono.error(new IllegalStateException("boo"))); + Mono mono = hm.invokeForRequest(this.exchange, this.model); - assertEquals(IllegalStateException.class, ex.getClass()); - assertEquals("Exception not wrapped with helpful argument details", - "Error resolving argument [0] of type [java.lang.String] on method " + - "[" + hm.getMethod().toGenericString() + "]", ex.getMessage()); + TestSubscriber.subscribe(mono) + .assertError(IllegalStateException.class) + .assertErrorMessage("Error resolving argument [0] of type [java.lang.String] " + + "on method [" + hm.getMethod().toGenericString() + "]"); } @Test - public void resolveArgumentWithErrorSignal() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); + public void resolverWithErrorSignal() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg", String.class); addResolver(hm, Mono.error(new IllegalStateException("boo"))); + Mono mono = hm.invokeForRequest(this.exchange, this.model); - Publisher publisher = hm.invokeForRequest(this.exchange, this.model); - Throwable ex = awaitErrorSignal(publisher); - - assertEquals(IllegalStateException.class, ex.getClass()); - assertEquals("Exception not wrapped with helpful argument details", - "Error resolving argument [0] of type [java.lang.String] on method " + - "[" + hm.getMethod().toGenericString() + "]", ex.getMessage()); + TestSubscriber.subscribe(mono) + .assertError(IllegalStateException.class) + .assertErrorMessage("Error resolving argument [0] of type [java.lang.String] " + + "on method [" + hm.getMethod().toGenericString() + "]"); } @Test - public void illegalArgumentExceptionIsWrappedWithHelpfulDetails() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("singleArg", String.class); + public void illegalArgumentExceptionIsWrappedWithInvocationDetails() throws Exception { + InvocableHandlerMethod hm = handlerMethod("singleArg", String.class); addResolver(hm, Mono.just(1)); + Mono mono = hm.invokeForRequest(this.exchange, this.model); - Publisher publisher = hm.invokeForRequest(this.exchange, this.model); - Throwable ex = awaitErrorSignal(publisher); - - assertEquals(IllegalStateException.class, ex.getClass()); - assertEquals("Failed to invoke controller with resolved arguments: " + - "[0][type=java.lang.Integer][value=1] " + - "on method [" + hm.getMethod().toGenericString() + "]", ex.getMessage()); + TestSubscriber.subscribe(mono) + .assertError(IllegalStateException.class) + .assertErrorMessage("Failed to invoke controller with resolved arguments: " + + "[0][type=java.lang.Integer][value=1] " + + "on method [" + hm.getMethod().toGenericString() + "]"); } @Test public void invocationTargetExceptionIsUnwrapped() throws Exception { - InvocableHandlerMethod hm = createHandlerMethod("exceptionMethod"); - - Publisher publisher = hm.invokeForRequest(this.exchange, this.model); - Throwable ex = awaitErrorSignal(publisher); + InvocableHandlerMethod hm = handlerMethod("exceptionMethod"); + Mono mono = hm.invokeForRequest(this.exchange, this.model); - assertEquals(IllegalStateException.class, ex.getClass()); - assertEquals("boo", ex.getMessage()); + TestSubscriber.subscribe(mono) + .assertError(IllegalStateException.class) + .assertErrorMessage("boo"); } - private InvocableHandlerMethod createHandlerMethod(String methodName, Class... argTypes) throws Exception { - Object controller = new TestController(); - Method method = controller.getClass().getMethod(methodName, argTypes); - return new InvocableHandlerMethod(new HandlerMethod(controller, method)); + private InvocableHandlerMethod handlerMethod(String name, Class... args) throws Exception { + Method method = ResolvableMethod.on(TestController.class).name(name).argumentTypes(args).resolve(); + return new InvocableHandlerMethod(new HandlerMethod(new TestController(), method)); } - private void addResolver(InvocableHandlerMethod handlerMethod, Publisher resolvedValue) { + private void addResolver(InvocableHandlerMethod handlerMethod, Mono resolvedValue) { HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class); when(resolver.supportsParameter(any())).thenReturn(true); - when(resolver.resolveArgument(any(), any(), any())).thenReturn(Mono.from(resolvedValue)); + when(resolver.resolveArgument(any(), any(), any())).thenReturn(resolvedValue); handlerMethod.setHandlerMethodArgumentResolvers(Collections.singletonList(resolver)); } - private Throwable awaitErrorSignal(Publisher publisher) throws Exception { - Signal signal = Flux.from(publisher).materialize().collectList().block().get(0); - assertEquals("Unexpected signal: " + signal, SignalKind.onError, signal.getType()); - return signal.getThrowable(); + private void assertHandlerResultValue(Mono mono, String expected) { + TestSubscriber.subscribe(mono).assertValuesWith(result -> { + Optional optional = result.getReturnValue(); + assertTrue(optional.isPresent()); + assertEquals(expected, optional.get()); + }); } @@ -218,7 +177,7 @@ public class InvocableHandlerMethodTests { return "success"; } - public String singleArg(@RequestParam(required=false) String q) { + public String singleArg(String q) { return "success:" + q; } @@ -227,5 +186,4 @@ public class InvocableHandlerMethodTests { } } - }