From 8ed22394cfdb9326f56bb2af2c26200c0fc5a511 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 2 Mar 2017 11:10:17 -0500 Subject: [PATCH] Refactor ResolvableMethod --- .../web/reactive/result/ResolvableMethod.java | 637 ++++++++++++++---- .../method/InvocableHandlerMethodTests.java | 85 ++- ...RequestMappingInfoHandlerMappingTests.java | 75 ++- .../ErrorsArgumentResolverTests.java | 24 +- .../HttpEntityArgumentResolverTests.java | 14 +- .../MessageReaderArgumentResolverTests.java | 46 +- .../MessageWriterResultHandlerTests.java | 71 +- ...lAttributeMethodArgumentResolverTests.java | 96 +-- .../RequestBodyArgumentResolverTests.java | 82 ++- ...questParamMethodArgumentResolverTests.java | 4 +- .../ResponseEntityResultHandlerTests.java | 108 +-- ...erverWebExchangeArgumentResolverTests.java | 26 +- .../ViewResolutionResultHandlerTests.java | 125 ++-- 13 files changed, 906 insertions(+), 487 deletions(-) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java index ca5105f24ab..52a0e32d5a2 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/ResolvableMethod.java @@ -22,217 +22,568 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Predicate; - +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.aopalliance.intercept.MethodInterceptor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.target.EmptyTargetSource; +import org.springframework.cglib.core.SpringNamingPolicy; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodProxy; import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.objenesis.ObjenesisException; +import org.springframework.objenesis.SpringObjenesis; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.web.reactive.result.method.InvocableHandlerMethod; /** - * Convenience class for use in tests to resolve a {@link Method} and/or any of - * its {@link MethodParameter}s based on some hints. + * Convenience class to resolve a method and its parameters based on hints. + * + *

Background

+ * + *

When testing annotated methods we create test classes such as + * "TestController" with a diverse range of method signatures representing + * supported annotations and argument types. It becomes challenging to use + * naming strategies to keep track of methods and arguments especially in + * combination variables for reflection metadata. + * + *

The idea with {@link ResolvableMethod} is NOT to rely on naming techniques + * but to use hints to zero in on method parameters. Especially in combination + * with {@link ResolvableType} such hints can be strongly typed and make tests + * more readable by being explicit about what is being tested and more robust + * since the provided hints have to match. + * + *

Common use cases: * - *

In tests we often create a class (e.g. TestController) with diverse method - * signatures and annotations to test with. Use of descriptive method and argument - * names combined with using reflection, it becomes challenging to read and write - * tests and it becomes necessary to navigate to the actual method declaration - * which is cumbersome and involves several steps. + *

1. Declared Return Type

* - *

The idea here is to provide enough hints to resolving a method uniquely - * where the hints document exactly what is being tested and there is usually no - * need to navigate to the actual method declaration. For example if testing - * response handling, the return type may be used as a hint: + * When testing return types it's common to have many methods with a unique + * return type, possibly with or without an annotation. * *

- * ResolvableMethod resolvableMethod = ResolvableMethod.onClass(TestController.class);
-
- * ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, View.class);
- * Method method = resolvableMethod.returning(type).resolve();
  *
- * type = ResolvableType.forClassWithGenerics(Mono.class, String.class);
- * method = resolvableMethod.returning(type).resolve();
+ * import static org.springframework.web.reactive.result.ResolvableMethod.on;
+ *
+ * // Return type
+ * on(TestController.class).resolveReturnType(Foo.class);
+ *
+ * // Annotation + return type
+ * on(TestController.class).annotated(ResponseBody.class).resolveReturnType(Bar.class);
+ *
+ * // Annotation not present
+ * on(TestController.class).isNotAnnotated(ResponseBody.class).resolveReturnType();
+ *
+ * // Annotation properties
+ * on(TestController.class)
+ *         .annotated(RequestMapping.class, patterns("/foo"), params("p"))
+ *         .annotated(ResponseBody.class)
+ *         .resolveReturnType();
+ * 
+ * + *

2. Method Arguments

+ * + * When testing method arguments it's more likely to have one or a small number + * of methods with a wide array of argument types and parameter annotations. + * + *
  *
- * // ...
+ * ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
+ *
+ * testMethod.arg(Foo.class);
+ * testMethod.annotated(RequestBody.class)).arg(Bar.class);
+ * testMethod.annotated(RequestBody.class), required()).arg(Bar.class);
+ * testMethod.notAnnotated(RequestBody.class)).arg(Bar.class);
  * 
* - *

Additional {@code resolve} methods provide options to obtain one of the method - * arguments or return type as a {@link MethodParameter}. + *

3. Mock Handler Method Invocation

+ * + * Locate a method by invoking it through a proxy of the target handler: + * + *
+ *
+ * ResolvableMethod.on(TestController.class).mockCall(o -> o.handle(null)).method();
+ * 
* * @author Rossen Stoyanchev */ public class ResolvableMethod { - private final Class objectClass; - - private final Object object; - - private String methodName; + private static final Log logger = LogFactory.getLog(ResolvableMethod.class); - private Class[] argumentTypes; + private static final SpringObjenesis objenesis = new SpringObjenesis(); - private ResolvableType returnType; - private final List> annotationTypes = new ArrayList<>(4); + private final Method method; - private final List> predicates = new ArrayList<>(4); + private ResolvableMethod(Method method) { + Assert.notNull(method, "method is required"); + this.method = method; + } - private ResolvableMethod(Class objectClass) { - Assert.notNull(objectClass, "Class must not be null"); - this.objectClass = objectClass; - this.object = null; + /** + * Return the resolved method. + */ + public Method method() { + return this.method; } - private ResolvableMethod(Object object) { - Assert.notNull(object, "Object must not be null"); - this.object = object; - this.objectClass = object.getClass(); + /** + * Return the declared return type of the resolved method. + */ + public MethodParameter returnType() { + return new MethodParameter(this.method, -1); } + /** + * Find a unique argument matching the given type. + * @param type the expected type + */ + public MethodParameter arg(Class type) { + return new ArgResolver().arg(type); + } /** - * Methods that match the given name (regardless of arguments). + * Find a unique argument matching the given type. + * @param type the expected type */ - public ResolvableMethod name(String methodName) { - this.methodName = methodName; - return this; + public MethodParameter arg(ResolvableType type) { + return new ArgResolver().arg(type); } /** - * Methods that match the given argument types. + * Filter on method arguments that have the given annotation. + * @param annotationType the annotation type + * @param filter optional filters on the annotation */ - public ResolvableMethod argumentTypes(Class... argumentTypes) { - this.argumentTypes = argumentTypes; - return this; + @SafeVarargs + public final ArgResolver annotated(Class annotationType, Predicate... filter) { + return new ArgResolver().annotated(annotationType, filter); } /** - * Methods declared to return the given type. + * Filter on method arguments that don't have the given annotation type(s). + * @param annotationTypes the annotation types */ - public ResolvableMethod returning(ResolvableType resolvableType) { - this.returnType = resolvableType; - return this; + @SafeVarargs + public final ArgResolver notAnnotated(Class... annotationTypes) { + return new ArgResolver().notAnnotated(annotationTypes); } /** - * Methods with the given annotation. + * Filter on method arguments using customer predicates. */ - public ResolvableMethod annotated(Class annotationType) { - this.annotationTypes.add(annotationType); - return this; + @SafeVarargs + public final ArgResolver filtered(Predicate... filter) { + return new ArgResolver().filtered(filter); + } + + + @Override + public String toString() { + return "ResolvableMethod=" + formatMethod(); } + private String formatMethod() { + return this.method().getName() + + Arrays.stream(this.method.getParameters()) + .map(p -> { + Annotation[] annots = p.getAnnotations(); + return (annots.length != 0 ? Arrays.toString(annots) : "") + " " + p; + }) + .collect(Collectors.joining(",\n\t", "(\n\t", "\n)")); + } + + /** - * Methods matching the given predicate. + * Main entry point providing access to a {@code ResolvableMethod} builder. */ - public final ResolvableMethod matching(Predicate methodPredicate) { - this.predicates.add(methodPredicate); - return this; - } - - - // Resolve methods - - public Method resolve() { - Set methods = MethodIntrospector.selectMethods(this.objectClass, - (ReflectionUtils.MethodFilter) method -> { - if (this.methodName != null && !this.methodName.equals(method.getName())) { - return false; - } - if (getReturnType() != null) { - // String comparison (ResolvableType's with different providers) - String actual = ResolvableType.forMethodReturnType(method).toString(); - if (!actual.equals(getReturnType()) && !Object.class.equals(method.getDeclaringClass())) { - return false; - } - } - else if (!ObjectUtils.isEmpty(this.argumentTypes)) { - if (!Arrays.equals(this.argumentTypes, method.getParameterTypes())) { - return false; - } - } - else if (this.annotationTypes.stream() - .filter(annotType -> AnnotationUtils.findAnnotation(method, annotType) == null) - .findFirst() - .isPresent()) { - return false; - } - else if (this.predicates.stream().filter(p -> !p.test(method)).findFirst().isPresent()) { - return false; - } - return true; - }); - - Assert.state(!methods.isEmpty(), () -> "No matching method: " + this); - Assert.state(methods.size() == 1, () -> "Multiple matching methods: " + this); - return methods.iterator().next(); - } - - private String getReturnType() { - return (this.returnType != null ? this.returnType.toString() : null); - } - - public InvocableHandlerMethod resolveHandlerMethod() { - Assert.state(this.object != null, "Object must not be null"); - return new InvocableHandlerMethod(this.object, resolve()); - } - - public MethodParameter resolveReturnType() { - Method method = resolve(); - return new MethodParameter(method, -1); + public static Builder on(Class objectClass) { + return new Builder<>(objectClass); } - @SafeVarargs - public final MethodParameter resolveParam(Predicate... predicates) { - return resolveParam(null, predicates); + + /** + * Builder for {@code ResolvableMethod}. + */ + public static class Builder { + + private final Class objectClass; + + private final List> filters = new ArrayList<>(4); + + + private Builder(Class objectClass) { + Assert.notNull(objectClass, "Class must not be null"); + this.objectClass = objectClass; + } + + + private void addFilter(String message, Predicate filter) { + this.filters.add(new LabeledPredicate<>(message, filter)); + } + + /** + * Filter on methods with the given name. + */ + public Builder named(String methodName) { + addFilter("methodName=" + methodName, m -> m.getName().equals(methodName)); + return this; + } + + /** + * Filter on methods with the given annotation type. + * @param annotationType the expected annotation type + * @param filter optional filters on the actual annotation + */ + @SafeVarargs + public final Builder annotated(Class annotationType, Predicate... filter) { + String message = "annotated=" + annotationType.getName(); + addFilter(message, m -> { + A annot = AnnotatedElementUtils.findMergedAnnotation(m, annotationType); + return (annot != null && Arrays.stream(filter).allMatch(f -> f.test(annot))); + }); + return this; + } + + /** + * Filter on methods not annotated with the given annotation type. + */ + public final Builder isNotAnnotated(Class annotationType) { + String message = "notAnnotated=" + annotationType.getName(); + addFilter(message, m -> AnnotationUtils.findAnnotation(m, annotationType) == null); + return this; + } + + /** + * Filter on methods returning the given type. + */ + public Builder returning(Class returnType) { + return returning(ResolvableType.forClass(returnType)); + } + + /** + * Filter on methods returning the given type. + */ + public Builder returning(ResolvableType resolvableType) { + String expected = resolvableType.toString(); + String message = "returnType=" + expected; + addFilter(message, m -> expected.equals(ResolvableType.forMethodReturnType(m).toString())); + return this; + } + + /** + * Add custom filters for matching methods. + */ + @SafeVarargs + public final Builder filtered(Predicate... filters) { + this.filters.addAll(Arrays.asList(filters)); + return this; + } + + /** + * Build a {@code ResolvableMethod} from the provided filters which must + * resolve to a unique, single method. + * + *

See additional resolveXxx shortcut methods going directly to + * {@link Method} or return type parameter. + * + * @throws IllegalStateException for no match or multiple matches + */ + public ResolvableMethod build() { + Set methods = MethodIntrospector.selectMethods(this.objectClass, this::isMatch); + Assert.state(!methods.isEmpty(), "No matching method: " + this); + Assert.state(methods.size() == 1, "Multiple matching methods: " + this + formatMethods(methods)); + return new ResolvableMethod(methods.iterator().next()); + } + + private boolean isMatch(Method method) { + return this.filters.stream().allMatch(p -> p.test(method)); + } + + private String formatMethods(Set methods) { + return "\nMatched:\n" + methods.stream() + .map(Method::toGenericString).collect(Collectors.joining(",\n\t", "[\n\t", "\n]")); + } + + public ResolvableMethod mockCall(Consumer invoker) { + MethodInvocationInterceptor interceptor = new MethodInvocationInterceptor(); + T proxy = initProxy(this.objectClass, interceptor); + invoker.accept(proxy); + Method method = interceptor.getInvokedMethod(); + return new ResolvableMethod(method); + } + + + // Build & Resolve shortcuts... + + /** + * Resolve and return the {@code Method} equivalent to: + *

{@code build().method()} + */ + public final Method resolveMethod() { + return build().method(); + } + + /** + * Resolve and return the {@code Method} equivalent to: + *

{@code named(methodName).build().method()} + */ + public Method resolveMethod(String methodName) { + return named(methodName).build().method(); + } + + /** + * Resolve and return the declared return type equivalent to: + *

{@code build().returnType()} + */ + public final MethodParameter resolveReturnType() { + return build().returnType(); + } + + /** + * Shortcut to the unique return type equivalent to: + *

{@code returning(returnType).build().returnType()} + */ + public MethodParameter resolveReturnType(Class returnType) { + return returning(returnType).build().returnType(); + } + + /** + * Shortcut to the unique return type equivalent to: + *

{@code returning(returnType).build().returnType()} + */ + public MethodParameter resolveReturnType(ResolvableType returnType) { + return returning(returnType).build().returnType(); + } + + + @Override + public String toString() { + return "ResolvableMethod.Builder[\n" + + "\tobjectClass = " + this.objectClass.getName() + ",\n" + + "\tfilters = " + formatFilters() + "\n]"; + } + + private String formatFilters() { + return this.filters.stream().map(Object::toString) + .collect(Collectors.joining(",\n\t\t", "[\n\t\t", "\n\t]")); + } } - @SafeVarargs - public final MethodParameter resolveParam(ResolvableType type, Predicate... predicates) { - List matches = new ArrayList<>(); - - Method method = resolve(); - for (int i = 0; i < method.getParameterCount(); i++) { - MethodParameter param = new MethodParameter(method, i); - if (type != null) { - if (!ResolvableType.forMethodParameter(param).toString().equals(type.toString())) { - continue; + @SuppressWarnings("unchecked") + private static T initProxy(Class type, MethodInvocationInterceptor interceptor) { + Assert.notNull(type, "'type' must not be null"); + if (type.isInterface()) { + ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE); + factory.addInterface(type); + factory.addInterface(Supplier.class); + factory.addAdvice(interceptor); + return (T) factory.getProxy(); + } + + else { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(type); + enhancer.setInterfaces(new Class[] {Supplier.class}); + enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); + enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); + + Class proxyClass = enhancer.createClass(); + Object proxy = null; + + if (objenesis.isWorthTrying()) { + try { + proxy = objenesis.newInstance(proxyClass, enhancer.getUseCache()); + } + catch (ObjenesisException ex) { + logger.debug("Objenesis failed, falling back to default constructor", ex); } } - if (!ObjectUtils.isEmpty(predicates)) { - if (Arrays.stream(predicates).filter(p -> !p.test(param)).findFirst().isPresent()) { - continue; + + if (proxy == null) { + try { + proxy = ReflectionUtils.accessibleConstructor(proxyClass).newInstance(); + } + catch (Throwable ex) { + throw new IllegalStateException("Unable to instantiate proxy " + + "via both Objenesis and default constructor fails as well", ex); } } - matches.add(param); - } - Assert.state(!matches.isEmpty(), () -> "No matching arg on " + method.toString()); - Assert.state(matches.size() == 1, () -> "Multiple matching args: " + matches + " on " + method.toString()); - return matches.get(0); + ((Factory) proxy).setCallbacks(new Callback[] {interceptor}); + return (T) proxy; + } } - @Override - public String toString() { - return "Class=" + this.objectClass + - ", name=" + (this.methodName != null ? this.methodName : "") + - ", returnType=" + (this.returnType != null ? this.returnType : "") + - ", annotations=" + this.annotationTypes; + /** + * Predicate with a descriptive label. + */ + private static class LabeledPredicate implements Predicate { + + private final String label; + + private final Predicate delegate; + + + private LabeledPredicate(String label, Predicate delegate) { + this.label = label; + this.delegate = delegate; + } + + + @Override + public boolean test(T method) { + return this.delegate.test(method); + } + + @Override + public Predicate and(Predicate other) { + return this.delegate.and(other); + } + + @Override + public Predicate negate() { + return this.delegate.negate(); + } + + @Override + public Predicate or(Predicate other) { + return this.delegate.or(other); + } + + @Override + public String toString() { + return this.label; + } } + /** + * Resolver for method arguments. + */ + public class ArgResolver { + + private final List> filters = new ArrayList<>(4); + + + @SafeVarargs + private ArgResolver(Predicate... filter) { + this.filters.addAll(Arrays.asList(filter)); + } + + + /** + * Filter on method arguments that have the given annotation. + * @param annotationType the annotation type + * @param filter optional filters on the annotation + */ + @SafeVarargs + public final ArgResolver annotated(Class annotationType, Predicate... filter) { + this.filters.add(param -> { + A annot = param.getParameterAnnotation(annotationType); + return (annot != null && Arrays.stream(filter).allMatch(f -> f.test(annot))); + }); + return this; + } + + /** + * Filter on method arguments that don't have the given annotations. + * @param annotationTypes the annotation types + */ + @SafeVarargs + public final ArgResolver notAnnotated(Class... annotationTypes) { + this.filters.add(p -> Arrays.stream(annotationTypes).noneMatch(p::hasParameterAnnotation)); + return this; + } + + /** + * Filter on method arguments using customer predicates. + */ + @SafeVarargs + public final ArgResolver filtered(Predicate... filter) { + this.filters.addAll(Arrays.asList(filter)); + return this; + } + + /** + * Resolve the argument also matching to the given type. + * @param type the expected type + */ + public MethodParameter arg(Class type) { + this.filters.add(p -> type.equals(p.getParameterType())); + return arg(ResolvableType.forClass(type)); + } + + /** + * Resolve the argument also matching to the given type. + * @param type the expected type + */ + public MethodParameter arg(ResolvableType type) { + this.filters.add(p -> type.toString().equals(ResolvableType.forMethodParameter(p).toString())); + return arg(); + } + + /** + * Resolve the argument. + */ + public final MethodParameter arg() { + List matches = applyFilters(); + Assert.state(!matches.isEmpty(), () -> "No matching arg in method\n" + formatMethod()); + Assert.state(matches.size() == 1, () -> "Multiple matching args in method\n" + formatMethod()); + return matches.get(0); + } - public static ResolvableMethod onClass(Class clazz) { - return new ResolvableMethod(clazz); + + private List applyFilters() { + List matches = new ArrayList<>(); + for (int i = 0; i < method.getParameterCount(); i++) { + MethodParameter param = new MethodParameter(method, i); + if (this.filters.stream().allMatch(p -> p.test(param))) { + matches.add(param); + } + } + return matches; + } } - public static ResolvableMethod on(Object object) { - return new ResolvableMethod(object); + private static class MethodInvocationInterceptor + implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor { + + private Method invokedMethod; + + + Method getInvokedMethod() { + return this.invokedMethod; + } + + @Override + public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) { + if (ReflectionUtils.isObjectMethod(method)) { + return ReflectionUtils.invokeMethod(method, object, args); + } + else { + this.invokedMethod = method; + return null; + } + } + + @Override + public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable { + return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null); + } } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java index f91ea233204..c794bfd5662 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/InvocableHandlerMethodTests.java @@ -16,7 +16,8 @@ package org.springframework.web.reactive.result.method; -import java.util.Collections; +import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Optional; import org.junit.Before; @@ -30,16 +31,20 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.HandlerResult; -import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.MockWebSessionManager; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.web.reactive.result.ResolvableMethod.on; /** * Unit tests for {@link InvocableHandlerMethod}. @@ -47,7 +52,6 @@ import static org.mockito.Mockito.*; * @author Rossen Stoyanchev * @author Juergen Hoeller */ -@SuppressWarnings("ThrowableResultOfMethodCallIgnored") public class InvocableHandlerMethodTests { private ServerWebExchange exchange; @@ -64,34 +68,36 @@ public class InvocableHandlerMethodTests { @Test public void invokeMethodWithNoArguments() throws Exception { - InvocableHandlerMethod hm = handlerMethod("noArgs"); - Mono mono = hm.invoke(this.exchange, new BindingContext()); - + Method method = on(TestController.class).mockCall(TestController::noArgs).method(); + Mono mono = invoke(new TestController(), method); assertHandlerResultValue(mono, "success"); } @Test public void invokeMethodWithNoValue() throws Exception { - InvocableHandlerMethod hm = handlerMethod("singleArg"); - addResolver(hm, Mono.empty()); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Mono resolvedValue = Mono.empty(); + Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method(); + Mono mono = invoke(new TestController(), method, resolverFor(resolvedValue)); assertHandlerResultValue(mono, "success:null"); } @Test public void invokeMethodWithValue() throws Exception { - InvocableHandlerMethod hm = handlerMethod("singleArg"); - addResolver(hm, Mono.just("value1")); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Mono resolvedValue = Mono.just("value1"); + Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method(); + Mono mono = invoke(new TestController(), method, resolverFor(resolvedValue)); assertHandlerResultValue(mono, "success:value1"); } @Test public void noMatchingResolver() throws Exception { - InvocableHandlerMethod hm = handlerMethod("singleArg"); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method(); + Mono mono = invoke(new TestController(), method); try { mono.block(); @@ -99,15 +105,16 @@ public class InvocableHandlerMethodTests { } catch (IllegalStateException ex) { assertThat(ex.getMessage(), is("No suitable resolver for argument 0 of type 'java.lang.String' " + - "on " + hm.getMethod().toGenericString())); + "on " + method.toGenericString())); } } @Test public void resolverThrowsException() throws Exception { - InvocableHandlerMethod hm = handlerMethod("singleArg"); - addResolver(hm, Mono.error(new UnsupportedMediaTypeStatusException("boo"))); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Mono resolvedValue = Mono.error(new UnsupportedMediaTypeStatusException("boo")); + Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method(); + Mono mono = invoke(new TestController(), method, resolverFor(resolvedValue)); try { mono.block(); @@ -120,9 +127,10 @@ public class InvocableHandlerMethodTests { @Test public void illegalArgumentExceptionIsWrappedWithInvocationDetails() throws Exception { - InvocableHandlerMethod hm = handlerMethod("singleArg"); - addResolver(hm, Mono.just(1)); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Mono resolvedValue = Mono.just(1); + Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method(); + Mono mono = invoke(new TestController(), method, resolverFor(resolvedValue)); try { mono.block(); @@ -131,14 +139,15 @@ public class InvocableHandlerMethodTests { catch (IllegalStateException ex) { assertThat(ex.getMessage(), is("Failed to invoke handler method with resolved arguments: " + "[0][type=java.lang.Integer][value=1] " + - "on " + hm.getMethod().toGenericString())); + "on " + method.toGenericString())); } } @Test public void invocationTargetExceptionIsUnwrapped() throws Exception { - InvocableHandlerMethod hm = handlerMethod("exceptionMethod"); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Method method = on(TestController.class).mockCall(TestController::exceptionMethod).method(); + Mono mono = invoke(new TestController(), method); try { mono.block(); @@ -151,24 +160,32 @@ public class InvocableHandlerMethodTests { @Test public void invokeMethodWithResponseStatus() throws Exception { - InvocableHandlerMethod hm = handlerMethod("responseStatus"); - Mono mono = hm.invoke(this.exchange, new BindingContext()); + + Method method = on(TestController.class).annotated(ResponseStatus.class).resolveMethod(); + Mono mono = invoke(new TestController(), method); assertHandlerResultValue(mono, "created"); assertThat(this.exchange.getResponse().getStatusCode(), is(HttpStatus.CREATED)); } - private InvocableHandlerMethod handlerMethod(String name) throws Exception { - TestController controller = new TestController(); - return ResolvableMethod.on(controller).name(name).resolveHandlerMethod(); + private Mono invoke(Object handler, Method method) { + return this.invoke(handler, method, new HandlerMethodArgumentResolver[0]); + } + + private Mono invoke(Object handler, Method method, + HandlerMethodArgumentResolver... resolver) { + + InvocableHandlerMethod hm = new InvocableHandlerMethod(handler, method); + hm.setArgumentResolvers(Arrays.asList(resolver)); + return hm.invoke(this.exchange, new BindingContext()); } - private void addResolver(InvocableHandlerMethod handlerMethod, Mono resolvedValue) { + private HandlerMethodArgumentResolver resolverFor(Mono resolvedValue) { HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class); when(resolver.supportsParameter(any())).thenReturn(true); when(resolver.resolveArgument(any(), any(), any())).thenReturn(resolvedValue); - handlerMethod.setArgumentResolvers(Collections.singletonList(resolver)); + return resolver; } private void assertHandlerResultValue(Mono mono, String expected) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java index 89e2e172554..c41b773f2ac 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; import org.junit.Before; import org.junit.Test; @@ -51,7 +52,7 @@ import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.result.ResolvableMethod; -import org.springframework.web.reactive.result.method.RequestMappingInfo.*; +import org.springframework.web.reactive.result.method.RequestMappingInfo.BuilderConfiguration; import org.springframework.web.server.MethodNotAllowedException; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; @@ -60,10 +61,16 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.support.HttpRequestPathHelper; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.web.bind.annotation.RequestMethod.*; -import static org.springframework.web.reactive.result.method.RequestMappingInfo.*; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.HEAD; +import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS; +import static org.springframework.web.reactive.result.method.RequestMappingInfo.paths; /** * Unit tests for {@link RequestMappingInfoHandlerMapping}. @@ -95,9 +102,10 @@ public class RequestMappingInfoHandlerMappingTests { @Test public void getHandlerDirectMatch() throws Exception { - String[] patterns = new String[] {"/foo"}; - String[] params = new String[] {}; - Method expected = resolveMethod(new TestController(), patterns, null, params); + + Method expected = ResolvableMethod.on(TestController.class) + .annotated(RequestMapping.class, patterns("/foo"), params()) + .resolveMethod(); this.request = MockServerHttpRequest.get("/foo").build(); HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block(); @@ -107,9 +115,10 @@ public class RequestMappingInfoHandlerMappingTests { @Test public void getHandlerGlobMatch() throws Exception { - String[] patterns = new String[] {"/ba*"}; - RequestMethod[] methods = new RequestMethod[] {GET, HEAD}; - Method expected = resolveMethod(new TestController(), patterns, methods, null); + + Method expected = ResolvableMethod.on(TestController.class) + .annotated(RequestMapping.class, patterns("/ba*"), methods(GET, HEAD)) + .resolveMethod(); this.request = MockServerHttpRequest.get("/bar").build(); HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block(); @@ -119,8 +128,10 @@ public class RequestMappingInfoHandlerMappingTests { @Test public void getHandlerEmptyPathMatch() throws Exception { - String[] patterns = new String[] {""}; - Method expected = resolveMethod(new TestController(), patterns, null, null); + + Method expected = ResolvableMethod.on(TestController.class) + .annotated(RequestMapping.class, patterns("")) + .resolveMethod(); this.request = MockServerHttpRequest.get("").build(); HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block(); @@ -133,9 +144,10 @@ public class RequestMappingInfoHandlerMappingTests { @Test public void getHandlerBestMatch() throws Exception { - String[] patterns = new String[] {"/foo"}; - String[] params = new String[] {"p"}; - Method expected = resolveMethod(new TestController(), patterns, null, params); + + Method expected = ResolvableMethod.on(TestController.class) + .annotated(RequestMapping.class, patterns("/foo"), params("p")) + .resolveMethod(); this.request = MockServerHttpRequest.get("/foo?p=anything").build(); HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block(); @@ -416,27 +428,16 @@ public class RequestMappingInfoHandlerMappingTests { return (Map) exchange.getAttributes().get(attrName); } - private Method resolveMethod(Object controller, String[] patterns, - RequestMethod[] methods, String[] params) { - - return ResolvableMethod.on(controller) - .matching(method -> { - RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class); - if (annot == null) { - return false; - } - else if (patterns != null && !Arrays.equals(annot.path(), patterns)) { - return false; - } - else if (methods != null && !Arrays.equals(annot.method(), methods)) { - return false; - } - else if (params != null && (!Arrays.equals(annot.params(), params))) { - return false; - } - return true; - }) - .resolve(); + private Predicate patterns(String... patterns) { + return rm -> Arrays.equals(patterns, rm.path()); + } + + private Predicate methods(RequestMethod... methods) { + return rm -> Arrays.equals(methods, rm.method()); + } + + private Predicate params(String... params) { + return rm -> Arrays.equals(params, rm.params()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ErrorsArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ErrorsArgumentResolverTests.java index e85fc67b827..9ae24350010 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ErrorsArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ErrorsArgumentResolverTests.java @@ -35,8 +35,9 @@ import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; -import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; /** * Unit tests for {@link ErrorsMethodArgumentResolver}. @@ -53,7 +54,7 @@ public class ErrorsArgumentResolverTests { private ServerWebExchange exchange; - private final ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle"); + private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @Before @@ -72,16 +73,16 @@ public class ErrorsArgumentResolverTests { @Test public void supports() throws Exception { - MethodParameter parameter = parameter(forClass(Errors.class)); + MethodParameter parameter = this.testMethod.arg(Errors.class); assertTrue(this.resolver.supportsParameter(parameter)); - parameter = parameter(forClass(BindingResult.class)); + parameter = this.testMethod.arg(BindingResult.class); assertTrue(this.resolver.supportsParameter(parameter)); - parameter = parameter(forClassWithGenerics(Mono.class, Errors.class)); + parameter = this.testMethod.arg(ResolvableType.forClassWithGenerics(Mono.class, Errors.class)); assertFalse(this.resolver.supportsParameter(parameter)); - parameter = parameter(forClass(String.class)); + parameter = this.testMethod.arg(String.class); assertFalse(this.resolver.supportsParameter(parameter)); } @@ -99,7 +100,7 @@ public class ErrorsArgumentResolverTests { @Test(expected = IllegalArgumentException.class) public void resolveErrorsAfterMonoModelAttribute() throws Exception { - MethodParameter parameter = parameter(forClass(BindingResult.class)); + MethodParameter parameter = this.testMethod.arg(BindingResult.class); this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange).blockMillis(5000); } @@ -109,7 +110,7 @@ public class ErrorsArgumentResolverTests { String key = BindingResult.MODEL_KEY_PREFIX + "foo"; this.bindingContext.getModel().asMap().put(key, bindingResult); - MethodParameter parameter = parameter(forClass(Errors.class)); + MethodParameter parameter = this.testMethod.arg(Errors.class); Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange) .blockMillis(5000); @@ -118,11 +119,6 @@ public class ErrorsArgumentResolverTests { } - private MethodParameter parameter(ResolvableType type) { - return this.testMethod.resolveParam(type); - } - - private static class Foo { private String name; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java index 9fad32acc5f..f07f1f326ae 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java @@ -66,7 +66,7 @@ public class HttpEntityArgumentResolverTests { private MockServerHttpRequest request; - private ResolvableMethod testMethod = ResolvableMethod.onClass(getClass()).name("handle"); + private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @Before @@ -99,10 +99,8 @@ public class HttpEntityArgumentResolverTests { @Test public void doesNotSupport() throws Exception { ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, String.class); - assertFalse(this.resolver.supportsParameter(this.testMethod.resolveParam(type))); - - type = ResolvableType.forClass(String.class); - assertFalse(this.resolver.supportsParameter(this.testMethod.resolveParam(type))); + assertFalse(this.resolver.supportsParameter(this.testMethod.arg(type))); + assertFalse(this.resolver.supportsParameter(this.testMethod.arg(String.class))); } @Test @@ -303,7 +301,7 @@ public class HttpEntityArgumentResolverTests { } private void testSupports(ResolvableType type) { - MethodParameter parameter = this.testMethod.resolveParam(type); + MethodParameter parameter = this.testMethod.arg(type); assertTrue(this.resolver.supportsParameter(parameter)); } @@ -314,7 +312,7 @@ public class HttpEntityArgumentResolverTests { .body(body); ServerWebExchange exchange = new DefaultServerWebExchange(this.request, new MockServerHttpResponse()); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono result = this.resolver.resolveArgument(param, new BindingContext(), exchange); Object value = result.block(Duration.ofSeconds(5)); @@ -328,7 +326,7 @@ public class HttpEntityArgumentResolverTests { @SuppressWarnings("unchecked") private HttpEntity resolveValueWithEmptyBody(ResolvableType type) { ServerWebExchange exchange = new DefaultServerWebExchange(this.request, new MockServerHttpResponse()); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono result = this.resolver.resolveArgument(param, new BindingContext(), exchange); HttpEntity httpEntity = (HttpEntity) result.block(Duration.ofSeconds(5)); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java index c146fbcf885..a270c19820b 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java @@ -58,8 +58,11 @@ import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.server.adapter.DefaultServerWebExchange; -import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.core.ResolvableType.forClassWithGenerics; /** * Unit tests for {@link AbstractMessageReaderArgumentResolver}. @@ -74,7 +77,7 @@ public class MessageReaderArgumentResolverTests { private BindingContext bindingContext; - private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle"); + private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @Before @@ -90,7 +93,7 @@ public class MessageReaderArgumentResolverTests { public void missingContentType() throws Exception { this.request = request().body("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"); ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono result = this.resolver.readBody(param, true, this.bindingContext, exchange()); StepVerifier.create(result).expectError(UnsupportedMediaTypeStatusException.class).verify(); @@ -102,7 +105,7 @@ public class MessageReaderArgumentResolverTests { public void emptyBody() throws Exception { this.request = request().header("Content-Type", "application/json").build(); ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono result = (Mono) this.resolver.readBody( param, true, this.bindingContext, exchange()).block(); @@ -113,7 +116,7 @@ public class MessageReaderArgumentResolverTests { public void monoTestBean() throws Exception { String body = "{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"; ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono mono = resolveValue(param, body); assertEquals(new TestBean("FOOFOO", "BARBAR"), mono.block()); @@ -123,7 +126,7 @@ public class MessageReaderArgumentResolverTests { public void fluxTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(Flux.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Flux flux = resolveValue(param, body); assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), @@ -134,7 +137,7 @@ public class MessageReaderArgumentResolverTests { public void singleTestBean() throws Exception { String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; ResolvableType type = forClassWithGenerics(Single.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Single single = resolveValue(param, body); assertEquals(new TestBean("f1", "b1"), single.toBlocking().value()); @@ -144,7 +147,7 @@ public class MessageReaderArgumentResolverTests { public void rxJava2SingleTestBean() throws Exception { String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; ResolvableType type = forClassWithGenerics(io.reactivex.Single.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); io.reactivex.Single single = resolveValue(param, body); assertEquals(new TestBean("f1", "b1"), single.blockingGet()); @@ -154,7 +157,7 @@ public class MessageReaderArgumentResolverTests { public void rxJava2MaybeTestBean() throws Exception { String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; ResolvableType type = forClassWithGenerics(Maybe.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Maybe maybe = resolveValue(param, body); assertEquals(new TestBean("f1", "b1"), maybe.blockingGet()); @@ -164,7 +167,7 @@ public class MessageReaderArgumentResolverTests { public void observableTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(Observable.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Observable observable = resolveValue(param, body); assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), @@ -175,7 +178,7 @@ public class MessageReaderArgumentResolverTests { public void rxJava2ObservableTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(io.reactivex.Observable.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); io.reactivex.Observable observable = resolveValue(param, body); assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), @@ -186,7 +189,7 @@ public class MessageReaderArgumentResolverTests { public void flowableTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(Flowable.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Flowable flowable = resolveValue(param, body); assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), @@ -197,7 +200,7 @@ public class MessageReaderArgumentResolverTests { public void futureTestBean() throws Exception { String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; ResolvableType type = forClassWithGenerics(CompletableFuture.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); CompletableFuture future = resolveValue(param, body); assertEquals(new TestBean("f1", "b1"), future.get()); @@ -206,7 +209,7 @@ public class MessageReaderArgumentResolverTests { @Test public void testBean() throws Exception { String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}"; - MethodParameter param = this.testMethod.resolveParam(forClass(TestBean.class)); + MethodParameter param = this.testMethod.arg(TestBean.class); TestBean value = resolveValue(param, body); assertEquals(new TestBean("f1", "b1"), value); @@ -219,7 +222,7 @@ public class MessageReaderArgumentResolverTests { map.put("foo", "f1"); map.put("bar", "b1"); ResolvableType type = forClassWithGenerics(Map.class, String.class, String.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Map actual = resolveValue(param, body); assertEquals(map, actual); @@ -229,7 +232,7 @@ public class MessageReaderArgumentResolverTests { public void list() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(List.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); List list = resolveValue(param, body); assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), list); @@ -239,7 +242,7 @@ public class MessageReaderArgumentResolverTests { public void monoList() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; ResolvableType type = forClassWithGenerics(Mono.class, forClassWithGenerics(List.class, TestBean.class)); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono mono = resolveValue(param, body); List list = (List) mono.block(Duration.ofSeconds(5)); @@ -249,8 +252,7 @@ public class MessageReaderArgumentResolverTests { @Test public void array() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"; - ResolvableType type = forClass(TestBean[].class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(TestBean[].class); TestBean[] value = resolveValue(param, body); assertArrayEquals(new TestBean[] {new TestBean("f1", "b1"), new TestBean("f2", "b2")}, value); @@ -261,7 +263,7 @@ public class MessageReaderArgumentResolverTests { public void validateMonoTestBean() throws Exception { String body = "{\"bar\":\"b1\"}"; ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Mono mono = resolveValue(param, body); StepVerifier.create(mono).expectNextCount(0).expectError(ServerWebInputException.class).verify(); @@ -272,7 +274,7 @@ public class MessageReaderArgumentResolverTests { public void validateFluxTestBean() throws Exception { String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\"}]"; ResolvableType type = forClassWithGenerics(Flux.class, TestBean.class); - MethodParameter param = this.testMethod.resolveParam(type); + MethodParameter param = this.testMethod.arg(type); Flux flux = resolveValue(param, body); StepVerifier.create(flux) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java index 1a6bfff8916..9b1b743fc33 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java @@ -55,15 +55,16 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; -import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.springframework.core.ResolvableType.forClassWithGenerics; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8; import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE; +import static org.springframework.web.reactive.result.ResolvableMethod.on; /** * Unit tests for {@link AbstractMessageWriterResultHandler}. @@ -89,8 +90,8 @@ public class MessageWriterResultHandlerTests { @Test // SPR-12894 public void useDefaultContentType() throws Exception { Resource body = new ClassPathResource("logo.png", getClass()); - ResolvableType type = ResolvableType.forType(Resource.class); - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + MethodParameter type = on(TestController.class).resolveReturnType(Resource.class); + this.resultHandler.writeBody(body, type, this.exchange).block(Duration.ofSeconds(5)); assertEquals("image/x-png", this.response.getHeaders().getFirst("Content-Type")); } @@ -101,26 +102,40 @@ public class MessageWriterResultHandlerTests { Collections.singleton(APPLICATION_JSON)); String body = "foo"; - ResolvableType type = ResolvableType.forType(String.class); - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + MethodParameter type = on(TestController.class).resolveReturnType(String.class); + this.resultHandler.writeBody(body, type, this.exchange).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); } @Test public void voidReturnType() throws Exception { - testVoidReturnType(null, ResolvableType.forType(void.class)); - testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class)); - testVoidReturnType(Completable.complete(), ResolvableType.forClass(Completable.class)); - testVoidReturnType(io.reactivex.Completable.complete(), ResolvableType.forClass(io.reactivex.Completable.class)); - testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class)); - testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class)); - testVoidReturnType(io.reactivex.Observable.empty(), ResolvableType.forClassWithGenerics(io.reactivex.Observable.class, Void.class)); - testVoidReturnType(Flowable.empty(), ResolvableType.forClassWithGenerics(Flowable.class, Void.class)); + testVoid(null, on(TestController.class).resolveReturnType(void.class)); + + testVoid(Mono.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Mono.class, Void.class))); + + testVoid(Flux.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Flux.class, Void.class))); + + testVoid(Completable.complete(), on(TestController.class) + .resolveReturnType(Completable.class)); + + testVoid(Observable.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Observable.class, Void.class))); + + testVoid(io.reactivex.Completable.complete(), on(TestController.class) + .resolveReturnType(io.reactivex.Completable.class)); + + testVoid(io.reactivex.Observable.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(io.reactivex.Observable.class, Void.class))); + + testVoid(Flowable.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Flowable.class, Void.class))); } - private void testVoidReturnType(Object body, ResolvableType type) { - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + private void testVoid(Object body, MethodParameter returnType) { + this.resultHandler.writeBody(body, returnType, this.exchange).block(Duration.ofSeconds(5)); assertNull(this.response.getHeaders().get("Content-Type")); StepVerifier.create(this.response.getBody()) @@ -130,19 +145,22 @@ public class MessageWriterResultHandlerTests { @Test // SPR-13135 public void unsupportedReturnType() throws Exception { ByteArrayOutputStream body = new ByteArrayOutputStream(); - ResolvableType type = ResolvableType.forType(OutputStream.class); + MethodParameter type = on(TestController.class).resolveReturnType(OutputStream.class); HttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); - Mono mono = createResultHandler(writer).writeBody(body, returnType(type), this.exchange); + Mono mono = createResultHandler(writer).writeBody(body, type, this.exchange); StepVerifier.create(mono).expectError(IllegalStateException.class).verify(); } @Test // SPR-12811 public void jacksonTypeOfListElement() throws Exception { + + MethodParameter returnType = on(TestController.class) + .resolveReturnType(forClassWithGenerics(List.class, ParentClass.class)); + List body = Arrays.asList(new Foo("foo"), new Bar("bar")); - ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ParentClass.class); - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + this.resultHandler.writeBody(body, returnType, this.exchange).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("[{\"type\":\"foo\",\"parentProperty\":\"foo\"}," + @@ -152,8 +170,8 @@ public class MessageWriterResultHandlerTests { @Test // SPR-13318 public void jacksonTypeWithSubType() throws Exception { SimpleBean body = new SimpleBean(123L, "foo"); - ResolvableType type = ResolvableType.forClass(Identifiable.class); - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + MethodParameter type = on(TestController.class).resolveReturnType(Identifiable.class); + this.resultHandler.writeBody(body, type, this.exchange).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("{\"id\":123,\"name\":\"foo\"}"); @@ -161,19 +179,18 @@ public class MessageWriterResultHandlerTests { @Test // SPR-13318 public void jacksonTypeWithSubTypeOfListElement() throws Exception { + + MethodParameter returnType = on(TestController.class) + .resolveReturnType(forClassWithGenerics(List.class, Identifiable.class)); + List body = Arrays.asList(new SimpleBean(123L, "foo"), new SimpleBean(456L, "bar")); - ResolvableType type = ResolvableType.forClassWithGenerics(List.class, Identifiable.class); - this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5)); + this.resultHandler.writeBody(body, returnType, this.exchange).block(Duration.ofSeconds(5)); assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType()); assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]"); } - private MethodParameter returnType(ResolvableType bodyType) { - return ResolvableMethod.onClass(TestController.class).returning(bodyType).resolveReturnType(); - } - private AbstractMessageWriterResultHandler createResultHandler(HttpMessageWriter... writers) { List> writerList; if (ObjectUtils.isEmpty(writers)) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java index 04c14aed4e4..bfabeb37cc4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java @@ -29,7 +29,6 @@ import rx.Single; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; @@ -56,7 +55,7 @@ public class ModelAttributeMethodArgumentResolverTests { private BindingContext bindContext; - private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle"); + private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @Before @@ -74,17 +73,17 @@ public class ModelAttributeMethodArgumentResolverTests { ModelAttributeMethodArgumentResolver resolver = new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), false); - ResolvableType type = forClass(Foo.class); - assertTrue(resolver.supportsParameter(parameter(type))); + MethodParameter param = this.testMethod.annotated(ModelAttribute.class).arg(Foo.class); + assertTrue(resolver.supportsParameter(param)); - type = forClassWithGenerics(Mono.class, Foo.class); - assertTrue(resolver.supportsParameter(parameter(type))); + param = this.testMethod.annotated(ModelAttribute.class).arg(forClassWithGenerics(Mono.class, Foo.class)); + assertTrue(resolver.supportsParameter(param)); - type = forClass(Foo.class); - assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + param = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + assertFalse(resolver.supportsParameter(param)); - type = forClassWithGenerics(Mono.class, Foo.class); - assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + param = this.testMethod.notAnnotated(ModelAttribute.class).arg(forClassWithGenerics(Mono.class, Foo.class)); + assertFalse(resolver.supportsParameter(param)); } @Test @@ -92,22 +91,22 @@ public class ModelAttributeMethodArgumentResolverTests { ModelAttributeMethodArgumentResolver resolver = new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), true); - ResolvableType type = forClass(Foo.class); - assertTrue(resolver.supportsParameter(parameterNotAnnotated(type))); + MethodParameter param = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + assertTrue(resolver.supportsParameter(param)); - type = forClassWithGenerics(Mono.class, Foo.class); - assertTrue(resolver.supportsParameter(parameterNotAnnotated(type))); + param = this.testMethod.notAnnotated(ModelAttribute.class).arg(forClassWithGenerics(Mono.class, Foo.class)); + assertTrue(resolver.supportsParameter(param)); - type = forClass(String.class); - assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + param = this.testMethod.notAnnotated(ModelAttribute.class).arg(String.class); + assertFalse(resolver.supportsParameter(param)); - type = forClassWithGenerics(Mono.class, String.class); - assertFalse(resolver.supportsParameter(parameterNotAnnotated(type))); + param = this.testMethod.notAnnotated(ModelAttribute.class).arg(forClassWithGenerics(Mono.class, String.class)); + assertFalse(resolver.supportsParameter(param)); } @Test public void createAndBind() throws Exception { - testBindFoo(forClass(Foo.class), value -> { + testBindFoo(this.testMethod.annotated(ModelAttribute.class).arg(Foo.class), value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); @@ -115,7 +114,11 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void createAndBindToMono() throws Exception { - testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> { + + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class) + .arg(forClassWithGenerics(Mono.class, Foo.class)); + + testBindFoo(parameter, mono -> { assertTrue(mono.getClass().getName(), mono instanceof Mono); Object value = ((Mono) mono).blockMillis(5000); assertEquals(Foo.class, value.getClass()); @@ -125,7 +128,11 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void createAndBindToSingle() throws Exception { - testBindFoo(forClassWithGenerics(Single.class, Foo.class), single -> { + + MethodParameter parameter = this.testMethod.annotated(ModelAttribute.class) + .arg(forClassWithGenerics(Single.class, Foo.class)); + + testBindFoo(parameter, single -> { assertTrue(single.getClass().getName(), single instanceof Single); Object value = ((Single) single).toBlocking().value(); assertEquals(Foo.class, value.getClass()); @@ -139,7 +146,8 @@ public class ModelAttributeMethodArgumentResolverTests { foo.setName("Jim"); this.bindContext.getModel().addAttribute(foo); - testBindFoo(forClass(Foo.class), value -> { + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + testBindFoo(parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); @@ -153,7 +161,8 @@ public class ModelAttributeMethodArgumentResolverTests { foo.setName("Jim"); this.bindContext.getModel().addAttribute("foo", Mono.just(foo)); - testBindFoo(forClass(Foo.class), value -> { + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + testBindFoo(parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); @@ -167,7 +176,8 @@ public class ModelAttributeMethodArgumentResolverTests { foo.setName("Jim"); this.bindContext.getModel().addAttribute("foo", Single.just(foo)); - testBindFoo(forClass(Foo.class), value -> { + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + testBindFoo(parameter, value -> { assertEquals(Foo.class, value.getClass()); return (Foo) value; }); @@ -181,7 +191,10 @@ public class ModelAttributeMethodArgumentResolverTests { foo.setName("Jim"); this.bindContext.getModel().addAttribute("foo", Mono.just(foo)); - testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> { + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class) + .arg(forClassWithGenerics(Mono.class, Foo.class)); + + testBindFoo(parameter, mono -> { assertTrue(mono.getClass().getName(), mono instanceof Mono); Object value = ((Mono) mono).blockMillis(5000); assertEquals(Foo.class, value.getClass()); @@ -189,9 +202,9 @@ public class ModelAttributeMethodArgumentResolverTests { }); } - private void testBindFoo(ResolvableType type, Function valueExtractor) throws Exception { + private void testBindFoo(MethodParameter param, Function valueExtractor) throws Exception { Object value = createResolver() - .resolveArgument(parameter(type), this.bindContext, exchange("name=Robert&age=25")) + .resolveArgument(param, this.bindContext, exchange("name=Robert&age=25")) .blockMillis(0); Foo foo = valueExtractor.apply(value); @@ -209,13 +222,18 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void validationError() throws Exception { - testValidationError(forClass(Foo.class), resolvedArgumentMono -> resolvedArgumentMono); + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class).arg(Foo.class); + testValidationError(parameter, Function.identity()); } @Test @SuppressWarnings("unchecked") public void validationErrorToMono() throws Exception { - testValidationError(forClassWithGenerics(Mono.class, Foo.class), + + MethodParameter parameter = this.testMethod.notAnnotated(ModelAttribute.class) + .arg(forClassWithGenerics(Mono.class, Foo.class)); + + testValidationError(parameter, resolvedArgumentMono -> { Object value = resolvedArgumentMono.blockMillis(5000); assertNotNull(value); @@ -227,7 +245,11 @@ public class ModelAttributeMethodArgumentResolverTests { @Test @SuppressWarnings("unchecked") public void validationErrorToSingle() throws Exception { - testValidationError(forClassWithGenerics(Single.class, Foo.class), + + MethodParameter parameter = this.testMethod.annotated(ModelAttribute.class) + .arg(forClassWithGenerics(Single.class, Foo.class)); + + testValidationError(parameter, resolvedArgumentMono -> { Object value = resolvedArgumentMono.blockMillis(5000); assertNotNull(value); @@ -236,11 +258,11 @@ public class ModelAttributeMethodArgumentResolverTests { }); } - private void testValidationError(ResolvableType type, Function, Mono> valueMonoExtractor) + private void testValidationError(MethodParameter param, Function, Mono> valueMonoExtractor) throws URISyntaxException { ServerWebExchange exchange = exchange("age=invalid"); - Mono mono = createResolver().resolveArgument(parameter(type), this.bindContext, exchange); + Mono mono = createResolver().resolveArgument(param, this.bindContext, exchange); mono = valueMonoExtractor.apply(mono); @@ -259,16 +281,6 @@ public class ModelAttributeMethodArgumentResolverTests { return new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry()); } - private MethodParameter parameter(ResolvableType type) { - return this.testMethod.resolveParam(type, - parameter -> parameter.hasParameterAnnotation(ModelAttribute.class)); - } - - private MethodParameter parameterNotAnnotated(ResolvableType type) { - return this.testMethod.resolveParam(type, - parameter -> !parameter.hasParameterAnnotations()); - } - private ServerWebExchange exchange(String formData) throws URISyntaxException { MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; MockServerHttpRequest request = MockServerHttpRequest.post("/").contentType(mediaType).body(formData); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java index 54edcfd740c..d6b24a50822 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java @@ -46,8 +46,12 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.adapter.DefaultServerWebExchange; -import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.core.ResolvableType.forClassWithGenerics; /** * Unit tests for {@link RequestBodyArgumentResolver}. When adding a test also @@ -60,7 +64,7 @@ public class RequestBodyArgumentResolverTests { private RequestBodyArgumentResolver resolver; - private ResolvableMethod testMethod; + private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @Before @@ -68,26 +72,24 @@ public class RequestBodyArgumentResolverTests { List> readers = new ArrayList<>(); readers.add(new DecoderHttpMessageReader<>(new StringDecoder())); this.resolver = new RequestBodyArgumentResolver(readers); - - this.testMethod = ResolvableMethod.onClass(getClass()).name("handle"); } @Test public void supports() throws Exception { + ResolvableType type = forClassWithGenerics(Mono.class, String.class); - MethodParameter param = this.testMethod.resolveParam(type, requestBody(true)); - assertTrue(this.resolver.supportsParameter(param)); + assertTrue(this.resolver.supportsParameter( + this.testMethod.annotated(RequestBody.class, required()).arg(type))); - MethodParameter parameter = this.testMethod.resolveParam(p -> !p.hasParameterAnnotations()); + MethodParameter parameter = this.testMethod.notAnnotated(RequestBody.class).arg(String.class); assertFalse(this.resolver.supportsParameter(parameter)); } @Test public void stringBody() throws Exception { String body = "line1"; - ResolvableType type = forClass(String.class); - MethodParameter param = this.testMethod.resolveParam(type, requestBody(true)); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(String.class); String value = resolveValue(param, body); assertEquals(body, value); @@ -95,13 +97,14 @@ public class RequestBodyArgumentResolverTests { @Test(expected = ServerWebInputException.class) public void emptyBodyWithString() throws Exception { - resolveValueWithEmptyBody(forClass(String.class), true); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(String.class); + resolveValueWithEmptyBody(param); } @Test public void emptyBodyWithStringNotRequired() throws Exception { - ResolvableType type = forClass(String.class); - String body = resolveValueWithEmptyBody(type, false); + MethodParameter param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(String.class); + String body = resolveValueWithEmptyBody(param); assertNull(body); } @@ -111,12 +114,14 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithMono() throws Exception { ResolvableType type = forClassWithGenerics(Mono.class, String.class); - StepVerifier.create((Mono) resolveValueWithEmptyBody(type, true)) + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + StepVerifier.create((Mono) resolveValueWithEmptyBody(param)) .expectNextCount(0) .expectError(ServerWebInputException.class) .verify(); - StepVerifier.create((Mono) resolveValueWithEmptyBody(type, false)) + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + StepVerifier.create((Mono) resolveValueWithEmptyBody(param)) .expectNextCount(0) .expectComplete() .verify(); @@ -127,12 +132,14 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithFlux() throws Exception { ResolvableType type = forClassWithGenerics(Flux.class, String.class); - StepVerifier.create((Flux) resolveValueWithEmptyBody(type, true)) + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + StepVerifier.create((Flux) resolveValueWithEmptyBody(param)) .expectNextCount(0) .expectError(ServerWebInputException.class) .verify(); - StepVerifier.create((Flux) resolveValueWithEmptyBody(type, false)) + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + StepVerifier.create((Flux) resolveValueWithEmptyBody(param)) .expectNextCount(0) .expectComplete() .verify(); @@ -142,13 +149,15 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithSingle() throws Exception { ResolvableType type = forClassWithGenerics(Single.class, String.class); - Single single = resolveValueWithEmptyBody(type, true); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + Single single = resolveValueWithEmptyBody(param); StepVerifier.create(RxReactiveStreams.toPublisher(single)) .expectNextCount(0) .expectError(ServerWebInputException.class) .verify(); - single = resolveValueWithEmptyBody(type, false); + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + single = resolveValueWithEmptyBody(param); StepVerifier.create(RxReactiveStreams.toPublisher(single)) .expectNextCount(0) .expectError(ServerWebInputException.class) @@ -159,13 +168,15 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithMaybe() throws Exception { ResolvableType type = forClassWithGenerics(Maybe.class, String.class); - Maybe maybe = resolveValueWithEmptyBody(type, true); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + Maybe maybe = resolveValueWithEmptyBody(param); StepVerifier.create(maybe.toFlowable()) .expectNextCount(0) .expectError(ServerWebInputException.class) .verify(); - maybe = resolveValueWithEmptyBody(type, false); + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + maybe = resolveValueWithEmptyBody(param); StepVerifier.create(maybe.toFlowable()) .expectNextCount(0) .expectComplete() @@ -176,13 +187,15 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithObservable() throws Exception { ResolvableType type = forClassWithGenerics(Observable.class, String.class); - Observable observable = resolveValueWithEmptyBody(type, true); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + Observable observable = resolveValueWithEmptyBody(param); StepVerifier.create(RxReactiveStreams.toPublisher(observable)) .expectNextCount(0) .expectError(ServerWebInputException.class) .verify(); - observable = resolveValueWithEmptyBody(type, false); + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + observable = resolveValueWithEmptyBody(param); StepVerifier.create(RxReactiveStreams.toPublisher(observable)) .expectNextCount(0) .expectComplete() @@ -193,20 +206,21 @@ public class RequestBodyArgumentResolverTests { public void emptyBodyWithCompletableFuture() throws Exception { ResolvableType type = forClassWithGenerics(CompletableFuture.class, String.class); - CompletableFuture future = resolveValueWithEmptyBody(type, true); + MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type); + CompletableFuture future = resolveValueWithEmptyBody(param); future.whenComplete((text, ex) -> { assertNull(text); assertNotNull(ex); }); - future = resolveValueWithEmptyBody(type, false); + param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type); + future = resolveValueWithEmptyBody(param); future.whenComplete((text, ex) -> { assertNotNull(text); assertNull(ex); }); } - @SuppressWarnings("unchecked") private T resolveValue(MethodParameter param, String body) { MockServerHttpRequest request = MockServerHttpRequest.post("/path").body(body); @@ -223,15 +237,14 @@ public class RequestBodyArgumentResolverTests { } @SuppressWarnings("unchecked") - private T resolveValueWithEmptyBody(ResolvableType bodyType, boolean isRequired) { + private T resolveValueWithEmptyBody(MethodParameter param) { MockServerHttpRequest request = MockServerHttpRequest.post("/path").build(); ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse()); - MethodParameter param = this.testMethod.resolveParam(bodyType, requestBody(isRequired)); Mono result = this.resolver.resolveArgument(param, new BindingContext(), exchange); Object value = result.block(Duration.ofSeconds(5)); if (value != null) { - assertTrue("Unexpected return value type: " + value, + assertTrue("Unexpected parameter type: " + value, param.getParameterType().isAssignableFrom(value.getClass())); } @@ -239,11 +252,12 @@ public class RequestBodyArgumentResolverTests { return (T) value; } - private Predicate requestBody(boolean required) { - return p -> { - RequestBody annotation = p.getParameterAnnotation(RequestBody.class); - return annotation != null && annotation.required() == required; - }; + private Predicate required() { + return RequestBody::required; + } + + private Predicate notRequired() { + return a -> !a.required(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java index 52e09fbec50..8eee1b4146e 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -82,7 +82,6 @@ public class RequestParamMethodArgumentResolverTests { this.paramNotRequired = new SynthesizingMethodParameter(method, 6); this.paramOptional = new SynthesizingMethodParameter(method, 7); - ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); initializer.setConversionService(new DefaultFormattingConversionService()); this.bindContext = new BindingContext(initializer); @@ -216,7 +215,8 @@ public class RequestParamMethodArgumentResolverTests { String stringNotAnnot, @RequestParam("name") String paramRequired, @RequestParam(name = "name", required = false) String paramNotRequired, - @RequestParam("name") Optional paramOptional) { + @RequestParam("name") Optional paramOptional, + @RequestParam Mono paramMono) { } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index 409317a1c74..3f7ac60ee45 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -55,13 +55,17 @@ import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; -import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; -import static org.junit.Assert.*; -import static org.springframework.core.ResolvableType.*; -import static org.springframework.http.ResponseEntity.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.core.ResolvableType.forClass; +import static org.springframework.core.ResolvableType.forClassWithGenerics; +import static org.springframework.http.ResponseEntity.notFound; +import static org.springframework.http.ResponseEntity.ok; +import static org.springframework.web.reactive.result.ResolvableMethod.on; /** * Unit tests for {@link ResponseEntityResultHandler}. When adding a test also @@ -116,25 +120,31 @@ public class ResponseEntityResultHandlerTests { public void supports() throws NoSuchMethodException { Object value = null; - ResolvableType type = responseEntity(String.class); - assertTrue(this.resultHandler.supports(handlerResult(value, type))); - type = forClassWithGenerics(Mono.class, responseEntity(String.class)); - assertTrue(this.resultHandler.supports(handlerResult(value, type))); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); - type = forClassWithGenerics(Single.class, responseEntity(String.class)); - assertTrue(this.resultHandler.supports(handlerResult(value, type))); + returnType = on(TestController.class).resolveReturnType(asyncEntity(Mono.class, String.class)); + assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); - type = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class)); - assertTrue(this.resultHandler.supports(handlerResult(value, type))); + returnType = on(TestController.class).resolveReturnType(asyncEntity(Single.class, String.class)); + assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); - // False + returnType = on(TestController.class).resolveReturnType(asyncEntity(CompletableFuture.class, String.class)); + assertTrue(this.resultHandler.supports(handlerResult(value, returnType))); + } - type = ResolvableType.forClass(String.class); - assertFalse(this.resultHandler.supports(handlerResult(value, type))); + @Test + @SuppressWarnings("ConstantConditions") + public void doesNotSupport() throws NoSuchMethodException { - type = ResolvableType.forClass(Completable.class); - assertFalse(this.resultHandler.supports(handlerResult(value, type))); + Object value = null; + + MethodParameter returnType = on(TestController.class).resolveReturnType(forClass(String.class)); + assertFalse(this.resultHandler.supports(handlerResult(value, returnType))); + + returnType = on(TestController.class).resolveReturnType(forClass(Completable.class)); + assertFalse(this.resultHandler.supports(handlerResult(value, returnType))); } @Test @@ -145,8 +155,8 @@ public class ResponseEntityResultHandlerTests { @Test public void statusCode() throws Exception { ResponseEntity value = ResponseEntity.noContent().build(); - ResolvableType type = responseEntity(Void.class); - HandlerResult result = handlerResult(value, type); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); + HandlerResult result = handlerResult(value, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertEquals(HttpStatus.NO_CONTENT, this.response.getStatusCode()); @@ -157,9 +167,9 @@ public class ResponseEntityResultHandlerTests { @Test public void headers() throws Exception { URI location = new URI("/path"); - ResolvableType type = responseEntity(Void.class); ResponseEntity value = ResponseEntity.created(location).build(); - HandlerResult result = handlerResult(value, type); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(Void.class)); + HandlerResult result = handlerResult(value, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertEquals(HttpStatus.CREATED, this.response.getStatusCode()); @@ -171,9 +181,10 @@ public class ResponseEntityResultHandlerTests { @Test public void handleResponseEntityWithNullBody() throws Exception { Object returnValue = Mono.just(notFound().build()); - ResolvableType returnType = forClassWithGenerics(Mono.class, responseEntity(String.class)); - HandlerResult result = handlerResult(returnValue, returnType); + MethodParameter type = on(TestController.class).resolveReturnType(asyncEntity(Mono.class, String.class)); + HandlerResult result = handlerResult(returnValue, type); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); + assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode()); assertResponseBodyIsEmpty(); } @@ -181,19 +192,19 @@ public class ResponseEntityResultHandlerTests { @Test public void handleReturnTypes() throws Exception { Object returnValue = ok("abc"); - ResolvableType returnType = responseEntity(String.class); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); - returnType = forClassWithGenerics(Mono.class, responseEntity(String.class)); + returnType = on(TestController.class).resolveReturnType(asyncEntity(Mono.class, String.class)); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); - returnType = forClassWithGenerics(Single.class, responseEntity(String.class)); + returnType = on(TestController.class).resolveReturnType(asyncEntity(Single.class, String.class)); testHandle(returnValue, returnType); returnValue = Mono.just(ok("abc")); - returnType = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class)); + returnType = on(TestController.class).resolveReturnType(asyncEntity(CompletableFuture.class, String.class)); testHandle(returnValue, returnType); } @@ -204,7 +215,8 @@ public class ResponseEntityResultHandlerTests { this.request = MockServerHttpRequest.get("/path").ifModifiedSince(currentTime.toEpochMilli()).build(); ResponseEntity entity = ok().lastModified(oneMinAgo.toEpochMilli()).body("body"); - HandlerResult result = handlerResult(entity, responseEntity(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, null, oneMinAgo); @@ -216,7 +228,8 @@ public class ResponseEntityResultHandlerTests { this.request = MockServerHttpRequest.get("/path").ifNoneMatch(etagValue).build(); ResponseEntity entity = ok().eTag(etagValue).body("body"); - HandlerResult result = handlerResult(entity, responseEntity(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, Instant.MIN); @@ -227,7 +240,8 @@ public class ResponseEntityResultHandlerTests { this.request = MockServerHttpRequest.get("/path").ifNoneMatch("unquoted").build(); ResponseEntity entity = ok().eTag("\"deadb33f8badf00d\"").body("body"); - HandlerResult result = handlerResult(entity, responseEntity(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -247,7 +261,8 @@ public class ResponseEntityResultHandlerTests { .build(); ResponseEntity entity = ok().eTag(eTag).lastModified(oneMinAgo.toEpochMilli()).body("body"); - HandlerResult result = handlerResult(entity, responseEntity(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, eTag, oneMinAgo); @@ -267,7 +282,8 @@ public class ResponseEntityResultHandlerTests { .build(); ResponseEntity entity = ok().eTag(newEtag).lastModified(oneMinAgo.toEpochMilli()).body("body"); - HandlerResult result = handlerResult(entity, responseEntity(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(entity(String.class)); + HandlerResult result = handlerResult(entity, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertConditionalResponse(HttpStatus.OK, "body", newEtag, oneMinAgo); @@ -281,9 +297,7 @@ public class ResponseEntityResultHandlerTests { Collections.singleton(MediaType.APPLICATION_JSON)); HandlerResult result = new HandlerResult(new TestController(), Mono.just(ok().body("body")), - ResolvableMethod.onClass(TestController.class) - .name("monoResponseEntityWildcard") - .resolveReturnType()); + on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, ResponseEntity.class))); this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); @@ -298,10 +312,9 @@ public class ResponseEntityResultHandlerTests { exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.APPLICATION_JSON)); - HandlerResult result = new HandlerResult(new TestController(), Mono.just(notFound().build()), - ResolvableMethod.onClass(TestController.class) - .name("monoResponseEntityWildcard") - .resolveReturnType()); + MethodParameter returnType = on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, ResponseEntity.class)); + + HandlerResult result = new HandlerResult(new TestController(), Mono.just(notFound().build()), returnType); this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); @@ -310,10 +323,10 @@ public class ResponseEntityResultHandlerTests { } - private void testHandle(Object returnValue, ResolvableType type) { + private void testHandle(Object returnValue, MethodParameter returnType) { initExchange(); - HandlerResult result = handlerResult(returnValue, type); + HandlerResult result = handlerResult(returnValue, returnType); this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5)); assertEquals(HttpStatus.OK, this.response.getStatusCode()); @@ -325,13 +338,16 @@ public class ResponseEntityResultHandlerTests { return new DefaultServerWebExchange(this.request, this.response); } - private ResolvableType responseEntity(Class bodyType) { - return forClassWithGenerics(ResponseEntity.class, ResolvableType.forClass(bodyType)); + private ResolvableType entity(Class bodyType) { + return forClassWithGenerics(ResponseEntity.class, forClass(bodyType)); + } + + private ResolvableType asyncEntity(Class asyncType, Class bodyType) { + return forClassWithGenerics(asyncType, entity(bodyType)); } - private HandlerResult handlerResult(Object returnValue, ResolvableType type) { - MethodParameter param = ResolvableMethod.onClass(TestController.class).returning(type).resolveReturnType(); - return new HandlerResult(new TestController(), returnValue, param); + private HandlerResult handlerResult(Object returnValue, MethodParameter returnType) { + return new HandlerResult(new TestController(), returnValue, returnType); } private void assertResponseBody(String responseBody) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolverTests.java index 252f0fd6d6d..73c08d488a5 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolverTests.java @@ -43,12 +43,12 @@ import static org.mockito.Mockito.*; */ public class ServerWebExchangeArgumentResolverTests { - private final ResolvableMethod testMethod = ResolvableMethod.onClass(getClass()).name("handle"); - private final ServerWebExchangeArgumentResolver resolver = new ServerWebExchangeArgumentResolver(); private ServerWebExchange exchange; + private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); + @Before public void setup() throws Exception { @@ -62,19 +62,19 @@ public class ServerWebExchangeArgumentResolverTests { @Test public void supportsParameter() throws Exception { - assertTrue(this.resolver.supportsParameter(parameter(ServerWebExchange.class))); - assertTrue(this.resolver.supportsParameter(parameter(ServerHttpRequest.class))); - assertTrue(this.resolver.supportsParameter(parameter(ServerHttpResponse.class))); - assertTrue(this.resolver.supportsParameter(parameter(HttpMethod.class))); - assertFalse(this.resolver.supportsParameter(parameter(String.class))); + assertTrue(this.resolver.supportsParameter(this.testMethod.arg(ServerWebExchange.class))); + assertTrue(this.resolver.supportsParameter(this.testMethod.arg(ServerHttpRequest.class))); + assertTrue(this.resolver.supportsParameter(this.testMethod.arg(ServerHttpResponse.class))); + assertTrue(this.resolver.supportsParameter(this.testMethod.arg(HttpMethod.class))); + assertFalse(this.resolver.supportsParameter(this.testMethod.arg(String.class))); } @Test public void resolveArgument() throws Exception { - testResolveArgument(parameter(ServerWebExchange.class), this.exchange); - testResolveArgument(parameter(ServerHttpRequest.class), this.exchange.getRequest()); - testResolveArgument(parameter(ServerHttpResponse.class), this.exchange.getResponse()); - testResolveArgument(parameter(HttpMethod.class), HttpMethod.GET); + testResolveArgument(this.testMethod.arg(ServerWebExchange.class), this.exchange); + testResolveArgument(this.testMethod.arg(ServerHttpRequest.class), this.exchange.getRequest()); + testResolveArgument(this.testMethod.arg(ServerHttpResponse.class), this.exchange.getResponse()); + testResolveArgument(this.testMethod.arg(HttpMethod.class), HttpMethod.GET); } @@ -83,10 +83,6 @@ public class ServerWebExchangeArgumentResolverTests { assertSame(expected, mono.block()); } - private MethodParameter parameter(Class parameterType) { - return this.testMethod.resolveParam(parameter -> parameterType.equals(parameter.getParameterType())); - } - @SuppressWarnings("unused") public void handle( diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java index cd270867998..cdd20ba2ccb 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandlerTests.java @@ -38,7 +38,6 @@ import rx.Single; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; -import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.support.DataBufferTestUtils; @@ -53,16 +52,19 @@ import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.accept.HeaderContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; -import org.springframework.web.reactive.result.ResolvableMethod; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; -import static java.nio.charset.StandardCharsets.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.springframework.core.ResolvableType.*; -import static org.springframework.http.MediaType.*; +import static java.nio.charset.StandardCharsets.UTF_8; +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.core.ResolvableType.forClass; +import static org.springframework.core.ResolvableType.forClassWithGenerics; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.web.reactive.result.ResolvableMethod.on; /** * ViewResolutionResultHandler relying on a canned {@link TestViewResolver} @@ -85,30 +87,34 @@ public class ViewResolutionResultHandlerTests { @Test public void supports() throws Exception { - testSupports(forClass(String.class), true); - testSupports(forClass(View.class), true); - testSupports(forClassWithGenerics(Mono.class, String.class), true); - testSupports(forClassWithGenerics(Mono.class, View.class), true); - testSupports(forClassWithGenerics(Single.class, String.class), true); - testSupports(forClassWithGenerics(Single.class, View.class), true); - testSupports(forClassWithGenerics(Mono.class, Void.class), true); - testSupports(forClass(Completable.class), true); - testSupports(forClass(Model.class), true); - testSupports(forClass(Map.class), true); - testSupports(forClass(TestBean.class), true); - testSupports(forClass(Integer.class), false); - testSupports(resolvableMethod().annotated(ModelAttribute.class), true); + + testSupports(on(TestController.class).resolveReturnType(String.class)); + testSupports(on(TestController.class).resolveReturnType(View.class)); + testSupports(on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, String.class))); + testSupports(on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, View.class))); + testSupports(on(TestController.class).resolveReturnType(forClassWithGenerics(Single.class, String.class))); + testSupports(on(TestController.class).resolveReturnType(forClassWithGenerics(Single.class, View.class))); + testSupports(on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, Void.class))); + testSupports(on(TestController.class).resolveReturnType(Completable.class)); + testSupports(on(TestController.class).resolveReturnType(Model.class)); + testSupports(on(TestController.class).resolveReturnType(Map.class)); + testSupports(on(TestController.class).resolveReturnType(TestBean.class)); + + testSupports(on(TestController.class).annotated(ModelAttribute.class).resolveReturnType()); } - private void testSupports(ResolvableType type, boolean result) { - testSupports(resolvableMethod().returning(type), result); + private void testSupports(MethodParameter returnType) { + ViewResolutionResultHandler resultHandler = resultHandler(mock(ViewResolver.class)); + HandlerResult handlerResult = new HandlerResult(new Object(), null, returnType, this.bindingContext); + assertTrue(resultHandler.supports(handlerResult)); } - private void testSupports(ResolvableMethod resolvableMethod, boolean result) { + @Test + public void doesNotSupport() throws Exception { + MethodParameter returnType = on(TestController.class).resolveReturnType(Integer.class); ViewResolutionResultHandler resultHandler = resultHandler(mock(ViewResolver.class)); - MethodParameter returnType = resolvableMethod.resolveReturnType(); HandlerResult handlerResult = new HandlerResult(new Object(), null, returnType, this.bindingContext); - assertEquals(result, resultHandler.supports(handlerResult)); + assertFalse(resultHandler.supports(handlerResult)); } @Test @@ -124,35 +130,36 @@ public class ViewResolutionResultHandlerTests { @Test public void handleReturnValueTypes() throws Exception { + Object returnValue; - ResolvableType returnType; + MethodParameter returnType; ViewResolver resolver = new TestViewResolver("account"); - returnType = forClass(View.class); + returnType = on(TestController.class).resolveReturnType(View.class); returnValue = new TestView("account"); testHandle("/path", returnType, returnValue, "account: {id=123}"); - returnType = forClassWithGenerics(Mono.class, View.class); + returnType = on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, View.class)); returnValue = Mono.just(new TestView("account")); testHandle("/path", returnType, returnValue, "account: {id=123}"); - returnType = forClass(String.class); + returnType = on(TestController.class).resolveReturnType(forClass(String.class)); returnValue = "account"; testHandle("/path", returnType, returnValue, "account: {id=123}", resolver); - returnType = forClassWithGenerics(Mono.class, String.class); + returnType = on(TestController.class).resolveReturnType(forClassWithGenerics(Mono.class, String.class)); returnValue = Mono.just("account"); testHandle("/path", returnType, returnValue, "account: {id=123}", resolver); - returnType = forClass(Model.class); + returnType = on(TestController.class).resolveReturnType(forClass(Model.class)); returnValue = new ConcurrentModel().addAttribute("name", "Joe"); testHandle("/account", returnType, returnValue, "account: {id=123, name=Joe}", resolver); - returnType = forClass(Map.class); + returnType = on(TestController.class).resolveReturnType(forClass(Map.class)); returnValue = Collections.singletonMap("name", "Joe"); testHandle("/account", returnType, returnValue, "account: {id=123, name=Joe}", resolver); - returnType = forClass(TestBean.class); + returnType = on(TestController.class).resolveReturnType(forClass(TestBean.class)); returnValue = new TestBean("Joe"); String responseBody = "account: {" + "id=123, " + @@ -162,14 +169,14 @@ public class ViewResolutionResultHandlerTests { "}"; testHandle("/account", returnType, returnValue, responseBody, resolver); - testHandle("/account", resolvableMethod().annotated(ModelAttribute.class), - 99L, "account: {id=123, num=99}", resolver); + returnType = on(TestController.class).annotated(ModelAttribute.class).resolveReturnType(); + testHandle("/account", returnType, 99L, "account: {id=123, num=99}", resolver); } @Test public void handleWithMultipleResolvers() throws Exception { Object returnValue = "profile"; - ResolvableType returnType = forClass(String.class); + MethodParameter returnType = on(TestController.class).resolveReturnType(String.class); ViewResolver[] resolvers = {new TestViewResolver("account"), new TestViewResolver("profile")}; testHandle("/account", returnType, returnValue, "profile: {id=123}", resolvers); @@ -177,15 +184,22 @@ public class ViewResolutionResultHandlerTests { @Test public void defaultViewName() throws Exception { - testDefaultViewName(null, forClass(String.class)); - testDefaultViewName(Mono.empty(), forClassWithGenerics(Mono.class, String.class)); - testDefaultViewName(Mono.empty(), forClassWithGenerics(Mono.class, Void.class)); - testDefaultViewName(Completable.complete(), forClass(Completable.class)); + + testDefaultViewName(null, on(TestController.class).resolveReturnType(String.class)); + + testDefaultViewName(Mono.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Mono.class, String.class))); + + testDefaultViewName(Mono.empty(), on(TestController.class) + .resolveReturnType(forClassWithGenerics(Mono.class, Void.class))); + + testDefaultViewName(Completable.complete(), on(TestController.class) + .resolveReturnType(forClass(Completable.class))); } - private void testDefaultViewName(Object returnValue, ResolvableType type) throws URISyntaxException { + private void testDefaultViewName(Object returnValue, MethodParameter returnType) throws URISyntaxException { this.bindingContext.getModel().addAttribute("id", "123"); - HandlerResult result = new HandlerResult(new Object(), returnValue, returnType(type), this.bindingContext); + HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext); ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account")); this.request = MockServerHttpRequest.get("/account").build(); @@ -207,7 +221,7 @@ public class ViewResolutionResultHandlerTests { @Test public void unresolvedViewName() throws Exception { String returnValue = "account"; - MethodParameter returnType = returnType(forClass(String.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(String.class); HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext); this.request = MockServerHttpRequest.get("/path").build(); @@ -223,7 +237,7 @@ public class ViewResolutionResultHandlerTests { @Test public void contentNegotiation() throws Exception { TestBean value = new TestBean("Joe"); - MethodParameter returnType = returnType(forClass(TestBean.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(TestBean.class); HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext); this.request = MockServerHttpRequest.get("/account").accept(APPLICATION_JSON).build(); @@ -246,7 +260,7 @@ public class ViewResolutionResultHandlerTests { @Test public void contentNegotiationWith406() throws Exception { TestBean value = new TestBean("Joe"); - MethodParameter returnType = returnType(forClass(TestBean.class)); + MethodParameter returnType = on(TestController.class).resolveReturnType(TestBean.class); HandlerResult handlerResult = new HandlerResult(new Object(), value, returnType, this.bindingContext); this.request = MockServerHttpRequest.get("/account").accept(APPLICATION_JSON).build(); @@ -269,8 +283,8 @@ public class ViewResolutionResultHandlerTests { .addAttribute("attr4", Observable.just(new TestBean("Bean1"), new TestBean("Bean2"))) .addAttribute("attr5", Mono.empty()); - ResolvableType type = forClass(void.class); - HandlerResult result = new HandlerResult(new Object(), null, returnType(type), this.bindingContext); + MethodParameter returnType = on(TestController.class).resolveReturnType(void.class); + HandlerResult result = new HandlerResult(new Object(), null, returnType, this.bindingContext); ViewResolutionResultHandler handler = resultHandler(new TestViewResolver("account")); this.request = MockServerHttpRequest.get("/account").build(); @@ -294,10 +308,6 @@ public class ViewResolutionResultHandlerTests { return new DefaultServerWebExchange(this.request, new MockServerHttpResponse()); } - private MethodParameter returnType(ResolvableType type) { - return resolvableMethod().returning(type).resolveReturnType(); - } - private ViewResolutionResultHandler resultHandler(ViewResolver... resolvers) { return resultHandler(Collections.emptyList(), resolvers); } @@ -310,23 +320,12 @@ public class ViewResolutionResultHandlerTests { return handler; } - private ResolvableMethod resolvableMethod() { - return ResolvableMethod.onClass(TestController.class); - } - - private ServerWebExchange testHandle(String path, ResolvableType returnType, Object returnValue, - String responseBody, ViewResolver... resolvers) throws URISyntaxException { - - return testHandle(path, resolvableMethod().returning(returnType), returnValue, responseBody, resolvers); - } - - private ServerWebExchange testHandle(String path, ResolvableMethod resolvableMethod, Object returnValue, + private ServerWebExchange testHandle(String path, MethodParameter returnType, Object returnValue, String responseBody, ViewResolver... resolvers) throws URISyntaxException { Model model = this.bindingContext.getModel(); model.asMap().clear(); model.addAttribute("id", "123"); - MethodParameter returnType = resolvableMethod.resolveReturnType(); HandlerResult result = new HandlerResult(new Object(), returnValue, returnType, this.bindingContext); this.request = MockServerHttpRequest.get(path).build(); ServerWebExchange exchange = createExchange();