/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.*;
import static org.springframework.web.method.ResolvableMethod.*;
/**
* Unit tests for {@link InvocableHandlerMethod}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class InvocableHandlerMethodTests {
private final MockServerWebExchange exchange =
MockServerHttpRequest.get("http://localhost:8080/path").toExchange();
@Test
public void invokeMethodWithNoArguments() throws Exception {
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 {
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 {
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 {
Method method = on(TestController.class).mockCall(o -> o.singleArg(null)).method();
Mono<HandlerResult> mono = invoke(new TestController(), method);
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("No suitable resolver for argument 0 of type 'java.lang.String' " +
"on " + method.toGenericString()));
}
}
@Test
public void resolverThrowsException() throws Exception {
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();
fail("Expected UnsupportedMediaTypeStatusException");
}
catch (UnsupportedMediaTypeStatusException ex) {
assertThat(ex.getMessage(), is("Response status 415 with reason \"boo\""));
}
}
@Test
public void illegalArgumentExceptionIsWrappedWithInvocationDetails() throws Exception {
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();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("Failed to invoke handler method with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on " + method.toGenericString()));
}
}
@Test
public void invocationTargetExceptionIsUnwrapped() throws Exception {
Method method = on(TestController.class).mockCall(TestController::exceptionMethod).method();
Mono<HandlerResult> mono = invoke(new TestController(), method);
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("boo"));
}
}
@Test
public void invokeMethodWithResponseStatus() throws Exception {
Method method = on(TestController.class).annotPresent(ResponseStatus.class).resolveMethod();
Mono<HandlerResult> mono = invoke(new TestController(), method);
assertHandlerResultValue(mono, "created");
assertThat(this.exchange.getResponse().getStatusCode(), is(HttpStatus.CREATED));
}
private Mono<HandlerResult> invoke(Object handler, Method method) {
return this.invoke(handler, method, new HandlerMethodArgumentResolver[0]);
}
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);
return resolver;
}
private void assertHandlerResultValue(Mono<HandlerResult> mono, String expected) {
StepVerifier.create(mono)
.consumeNextWith(result -> {
Optional<?> optional = result.getReturnValue();
assertTrue(optional.isPresent());
assertEquals(expected, optional.get());
})
.expectComplete()
.verify();
}
@SuppressWarnings("unused")
private static class TestController {
public String noArgs() {
return "success";
}
public String singleArg(String q) {
return "success:" + q;
}
public void exceptionMethod() {
throw new IllegalStateException("boo");
}
@ResponseStatus(HttpStatus.CREATED)
public String responseStatus() {
return "created";
}
}
}