Refactor ResolvableMethod
This commit is contained in:
parent
e38854a72f
commit
8ed22394cf
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <h1>Background</h1>
|
||||
*
|
||||
* <p>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:
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Common use cases:
|
||||
*
|
||||
* <h2>1. Declared Return Type</h2>
|
||||
*
|
||||
* When testing return types it's common to have many methods with a unique
|
||||
* return type, possibly with or without an annotation.
|
||||
*
|
||||
* <pre>
|
||||
* 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();
|
||||
* </pre>
|
||||
*
|
||||
* <p>Additional {@code resolve} methods provide options to obtain one of the method
|
||||
* arguments or return type as a {@link MethodParameter}.
|
||||
* <h2>2. Method Arguments</h2>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* 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);
|
||||
* </pre>
|
||||
*
|
||||
* <h3>3. Mock Handler Method Invocation</h3>
|
||||
*
|
||||
* Locate a method by invoking it through a proxy of the target handler:
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* ResolvableMethod.on(TestController.class).mockCall(o -> o.handle(null)).method();
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ResolvableMethod {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
private static final Log logger = LogFactory.getLog(ResolvableMethod.class);
|
||||
|
||||
private final Object object;
|
||||
|
||||
private String methodName;
|
||||
|
||||
private Class<?>[] argumentTypes;
|
||||
|
||||
private ResolvableType returnType;
|
||||
|
||||
private final List<Class<? extends Annotation>> annotationTypes = new ArrayList<>(4);
|
||||
|
||||
private final List<Predicate<Method>> predicates = new ArrayList<>(4);
|
||||
private static final SpringObjenesis objenesis = new SpringObjenesis();
|
||||
|
||||
|
||||
private final Method method;
|
||||
|
||||
private ResolvableMethod(Class<?> objectClass) {
|
||||
Assert.notNull(objectClass, "Class must not be null");
|
||||
this.objectClass = objectClass;
|
||||
this.object = null;
|
||||
}
|
||||
|
||||
private ResolvableMethod(Object object) {
|
||||
Assert.notNull(object, "Object must not be null");
|
||||
this.object = object;
|
||||
this.objectClass = object.getClass();
|
||||
private ResolvableMethod(Method method) {
|
||||
Assert.notNull(method, "method is required");
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Methods that match the given name (regardless of arguments).
|
||||
* Return the resolved method.
|
||||
*/
|
||||
public ResolvableMethod name(String methodName) {
|
||||
this.methodName = methodName;
|
||||
return this;
|
||||
public Method method() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that match the given argument types.
|
||||
* Return the declared return type of the resolved method.
|
||||
*/
|
||||
public ResolvableMethod argumentTypes(Class<?>... argumentTypes) {
|
||||
this.argumentTypes = argumentTypes;
|
||||
return this;
|
||||
public MethodParameter returnType() {
|
||||
return new MethodParameter(this.method, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods declared to return the given type.
|
||||
* Find a unique argument matching the given type.
|
||||
* @param type the expected type
|
||||
*/
|
||||
public ResolvableMethod returning(ResolvableType resolvableType) {
|
||||
this.returnType = resolvableType;
|
||||
return this;
|
||||
public MethodParameter arg(Class<?> type) {
|
||||
return new ArgResolver().arg(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods with the given annotation.
|
||||
* Find a unique argument matching the given type.
|
||||
* @param type the expected type
|
||||
*/
|
||||
public ResolvableMethod annotated(Class<? extends Annotation> annotationType) {
|
||||
this.annotationTypes.add(annotationType);
|
||||
return this;
|
||||
public MethodParameter arg(ResolvableType type) {
|
||||
return new ArgResolver().arg(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods matching the given predicate.
|
||||
* Filter on method arguments that have the given annotation.
|
||||
* @param annotationType the annotation type
|
||||
* @param filter optional filters on the annotation
|
||||
*/
|
||||
public final ResolvableMethod matching(Predicate<Method> methodPredicate) {
|
||||
this.predicates.add(methodPredicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// Resolve methods
|
||||
|
||||
public Method resolve() {
|
||||
Set<Method> 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);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final MethodParameter resolveParam(Predicate<MethodParameter>... predicates) {
|
||||
return resolveParam(null, predicates);
|
||||
public final <A extends Annotation> ArgResolver annotated(Class<A> annotationType, Predicate<A>... filter) {
|
||||
return new ArgResolver().annotated(annotationType, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter on method arguments that don't have the given annotation type(s).
|
||||
* @param annotationTypes the annotation types
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final MethodParameter resolveParam(ResolvableType type, Predicate<MethodParameter>... predicates) {
|
||||
List<MethodParameter> 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;
|
||||
}
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(predicates)) {
|
||||
if (Arrays.stream(predicates).filter(p -> !p.test(param)).findFirst().isPresent()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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);
|
||||
public final ArgResolver notAnnotated(Class<? extends Annotation>... annotationTypes) {
|
||||
return new ArgResolver().notAnnotated(annotationTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter on method arguments using customer predicates.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final ArgResolver filtered(Predicate<MethodParameter>... filter) {
|
||||
return new ArgResolver().filtered(filter);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Class=" + this.objectClass +
|
||||
", name=" + (this.methodName != null ? this.methodName : "<not specified>") +
|
||||
", returnType=" + (this.returnType != null ? this.returnType : "<not specified>") +
|
||||
", annotations=" + this.annotationTypes;
|
||||
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)"));
|
||||
}
|
||||
|
||||
|
||||
public static ResolvableMethod onClass(Class<?> clazz) {
|
||||
return new ResolvableMethod(clazz);
|
||||
/**
|
||||
* Main entry point providing access to a {@code ResolvableMethod} builder.
|
||||
*/
|
||||
public static <T> Builder<T> on(Class<T> objectClass) {
|
||||
return new Builder<>(objectClass);
|
||||
}
|
||||
|
||||
public static ResolvableMethod on(Object object) {
|
||||
return new ResolvableMethod(object);
|
||||
|
||||
/**
|
||||
* Builder for {@code ResolvableMethod}.
|
||||
*/
|
||||
public static class Builder<T> {
|
||||
|
||||
private final Class<?> objectClass;
|
||||
|
||||
private final List<Predicate<Method>> 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<Method> 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 <A extends Annotation> Builder annotated(Class<A> annotationType, Predicate<A>... 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 <A extends Annotation> Builder isNotAnnotated(Class<A> 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<Method>... 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.
|
||||
*
|
||||
* <p>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<Method> 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<Method> methods) {
|
||||
return "\nMatched:\n" + methods.stream()
|
||||
.map(Method::toGenericString).collect(Collectors.joining(",\n\t", "[\n\t", "\n]"));
|
||||
}
|
||||
|
||||
public ResolvableMethod mockCall(Consumer<T> 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:
|
||||
* <p>{@code build().method()}
|
||||
*/
|
||||
public final Method resolveMethod() {
|
||||
return build().method();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve and return the {@code Method} equivalent to:
|
||||
* <p>{@code named(methodName).build().method()}
|
||||
*/
|
||||
public Method resolveMethod(String methodName) {
|
||||
return named(methodName).build().method();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve and return the declared return type equivalent to:
|
||||
* <p>{@code build().returnType()}
|
||||
*/
|
||||
public final MethodParameter resolveReturnType() {
|
||||
return build().returnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to the unique return type equivalent to:
|
||||
* <p>{@code returning(returnType).build().returnType()}
|
||||
*/
|
||||
public MethodParameter resolveReturnType(Class<?> returnType) {
|
||||
return returning(returnType).build().returnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to the unique return type equivalent to:
|
||||
* <p>{@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]"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> 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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
((Factory) proxy).setCallbacks(new Callback[] {interceptor});
|
||||
return (T) proxy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate with a descriptive label.
|
||||
*/
|
||||
private static class LabeledPredicate<T> implements Predicate<T> {
|
||||
|
||||
private final String label;
|
||||
|
||||
private final Predicate<T> delegate;
|
||||
|
||||
|
||||
private LabeledPredicate(String label, Predicate<T> delegate) {
|
||||
this.label = label;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean test(T method) {
|
||||
return this.delegate.test(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> and(Predicate<? super T> other) {
|
||||
return this.delegate.and(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> negate() {
|
||||
return this.delegate.negate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<T> or(Predicate<? super T> other) {
|
||||
return this.delegate.or(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver for method arguments.
|
||||
*/
|
||||
public class ArgResolver {
|
||||
|
||||
private final List<Predicate<MethodParameter>> filters = new ArrayList<>(4);
|
||||
|
||||
|
||||
@SafeVarargs
|
||||
private ArgResolver(Predicate<MethodParameter>... 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 <A extends Annotation> ArgResolver annotated(Class<A> annotationType, Predicate<A>... 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<? extends Annotation>... 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<MethodParameter>... 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<MethodParameter> 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);
|
||||
}
|
||||
|
||||
|
||||
private List<MethodParameter> applyFilters() {
|
||||
List<MethodParameter> 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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Method method = on(TestController.class).mockCall(TestController::noArgs).method();
|
||||
Mono<HandlerResult> mono = invoke(new TestController(), method);
|
||||
assertHandlerResultValue(mono, "success");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMethodWithNoValue() throws Exception {
|
||||
InvocableHandlerMethod hm = handlerMethod("singleArg");
|
||||
addResolver(hm, Mono.empty());
|
||||
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Mono<Object> resolvedValue = Mono.empty();
|
||||
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
|
||||
Mono<HandlerResult> 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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Mono<Object> resolvedValue = Mono.just("value1");
|
||||
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
|
||||
Mono<HandlerResult> mono = invoke(new TestController(), method, resolverFor(resolvedValue));
|
||||
|
||||
assertHandlerResultValue(mono, "success:value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMatchingResolver() throws Exception {
|
||||
InvocableHandlerMethod hm = handlerMethod("singleArg");
|
||||
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
|
||||
Mono<HandlerResult> 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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Mono<Object> resolvedValue = Mono.error(new UnsupportedMediaTypeStatusException("boo"));
|
||||
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
|
||||
Mono<HandlerResult> 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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Mono<Object> resolvedValue = Mono.just(1);
|
||||
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
|
||||
Mono<HandlerResult> 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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Method method = on(TestController.class).mockCall(TestController::exceptionMethod).method();
|
||||
Mono<HandlerResult> 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<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
|
||||
|
||||
Method method = on(TestController.class).annotated(ResponseStatus.class).resolveMethod();
|
||||
Mono<HandlerResult> 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<HandlerResult> invoke(Object handler, Method method) {
|
||||
return this.invoke(handler, method, new HandlerMethodArgumentResolver[0]);
|
||||
}
|
||||
|
||||
private void addResolver(InvocableHandlerMethod handlerMethod, Mono<Object> resolvedValue) {
|
||||
private Mono<HandlerResult> 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 <T> HandlerMethodArgumentResolver resolverFor(Mono<Object> 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<HandlerResult> mono, String expected) {
|
||||
|
|
|
|||
|
|
@ -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<String, String>) exchange.getAttributes().get(attrName);
|
||||
}
|
||||
|
||||
private Method resolveMethod(Object controller, String[] patterns,
|
||||
RequestMethod[] methods, String[] params) {
|
||||
private Predicate<RequestMapping> patterns(String... patterns) {
|
||||
return rm -> Arrays.equals(patterns, rm.path());
|
||||
}
|
||||
|
||||
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<RequestMapping> methods(RequestMethod... methods) {
|
||||
return rm -> Arrays.equals(methods, rm.method());
|
||||
}
|
||||
|
||||
private Predicate<RequestMapping> params(String... params) {
|
||||
return rm -> Arrays.equals(params, rm.params());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Object> 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 <T> HttpEntity<T> resolveValueWithEmptyBody(ResolvableType type) {
|
||||
ServerWebExchange exchange = new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
|
||||
MethodParameter param = this.testMethod.resolveParam(type);
|
||||
MethodParameter param = this.testMethod.arg(type);
|
||||
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
|
||||
HttpEntity<String> httpEntity = (HttpEntity<String>) result.block(Duration.ofSeconds(5));
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Object> 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<TestBean> result = (Mono<TestBean>) 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<Object> 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<TestBean> 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<TestBean> 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<TestBean> 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<TestBean> 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<TestBean> 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<TestBean> flux = resolveValue(param, body);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
|
|
|
|||
|
|
@ -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<Void> mono = createResultHandler(writer).writeBody(body, returnType(type), this.exchange);
|
||||
Mono<Void> 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<ParentClass> 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<SimpleBean> 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<HttpMessageWriter<?>> writerList;
|
||||
if (ObjectUtils.isEmpty(writers)) {
|
||||
|
|
|
|||
|
|
@ -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<Object, Foo> valueExtractor) throws Exception {
|
||||
private void testBindFoo(MethodParameter param, Function<Object, Foo> 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<?>, Mono<?>> valueMonoExtractor)
|
||||
private void testValidationError(MethodParameter param, Function<Mono<?>, 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);
|
||||
|
|
|
|||
|
|
@ -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<HttpMessageReader<?>> 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));
|
||||
|
||||
MethodParameter parameter = this.testMethod.resolveParam(p -> !p.hasParameterAnnotations());
|
||||
ResolvableType type = forClassWithGenerics(Mono.class, String.class);
|
||||
assertTrue(this.resolver.supportsParameter(
|
||||
this.testMethod.annotated(RequestBody.class, required()).arg(type)));
|
||||
|
||||
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<Void>) resolveValueWithEmptyBody(type, true))
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
StepVerifier.create((Mono<Void>) resolveValueWithEmptyBody(param))
|
||||
.expectNextCount(0)
|
||||
.expectError(ServerWebInputException.class)
|
||||
.verify();
|
||||
|
||||
StepVerifier.create((Mono<Void>) resolveValueWithEmptyBody(type, false))
|
||||
param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type);
|
||||
StepVerifier.create((Mono<Void>) 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<Void>) resolveValueWithEmptyBody(type, true))
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
StepVerifier.create((Flux<Void>) resolveValueWithEmptyBody(param))
|
||||
.expectNextCount(0)
|
||||
.expectError(ServerWebInputException.class)
|
||||
.verify();
|
||||
|
||||
StepVerifier.create((Flux<Void>) resolveValueWithEmptyBody(type, false))
|
||||
param = this.testMethod.annotated(RequestBody.class, notRequired()).arg(type);
|
||||
StepVerifier.create((Flux<Void>) 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<String> single = resolveValueWithEmptyBody(type, true);
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
Single<String> 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<String> maybe = resolveValueWithEmptyBody(type, true);
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
Maybe<String> 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<String> observable = resolveValueWithEmptyBody(type, true);
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
Observable<String> 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<String> future = resolveValueWithEmptyBody(type, true);
|
||||
MethodParameter param = this.testMethod.annotated(RequestBody.class, required()).arg(type);
|
||||
CompletableFuture<String> 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> T resolveValue(MethodParameter param, String body) {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/path").body(body);
|
||||
|
|
@ -223,15 +237,14 @@ public class RequestBodyArgumentResolverTests {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T resolveValueWithEmptyBody(ResolvableType bodyType, boolean isRequired) {
|
||||
private <T> 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<Object> 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<MethodParameter> requestBody(boolean required) {
|
||||
return p -> {
|
||||
RequestBody annotation = p.getParameterAnnotation(RequestBody.class);
|
||||
return annotation != null && annotation.required() == required;
|
||||
};
|
||||
private Predicate<RequestBody> required() {
|
||||
return RequestBody::required;
|
||||
}
|
||||
|
||||
private Predicate<RequestBody> notRequired() {
|
||||
return a -> !a.required();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Integer> paramOptional) {
|
||||
@RequestParam("name") Optional<Integer> paramOptional,
|
||||
@RequestParam Mono<String> paramMono) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Void> 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<Void> 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<String> 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<String> 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<String> 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<String> 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<String> 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 HandlerResult handlerResult(Object returnValue, ResolvableType type) {
|
||||
MethodParameter param = ResolvableMethod.onClass(TestController.class).returning(type).resolveReturnType();
|
||||
return new HandlerResult(new TestController(), returnValue, param);
|
||||
private ResolvableType asyncEntity(Class<?> asyncType, Class<?> bodyType) {
|
||||
return forClassWithGenerics(asyncType, entity(bodyType));
|
||||
}
|
||||
|
||||
private HandlerResult handlerResult(Object returnValue, MethodParameter returnType) {
|
||||
return new HandlerResult(new TestController(), returnValue, returnType);
|
||||
}
|
||||
|
||||
private void assertResponseBody(String responseBody) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(ResolvableMethod resolvableMethod, boolean result) {
|
||||
private void testSupports(MethodParameter returnType) {
|
||||
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));
|
||||
assertTrue(resultHandler.supports(handlerResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotSupport() throws Exception {
|
||||
MethodParameter returnType = on(TestController.class).resolveReturnType(Integer.class);
|
||||
ViewResolutionResultHandler resultHandler = resultHandler(mock(ViewResolver.class));
|
||||
HandlerResult handlerResult = new HandlerResult(new Object(), null, returnType, this.bindingContext);
|
||||
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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue