Add AssertJ support for the HTTP handler
This commit adds AssertJ compatible assertions for the component that produces the result from the request. See gh-21178
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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
|
||||||
|
*
|
||||||
|
* https://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.test.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
|
||||||
|
* to a {@link Method}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.2
|
||||||
|
*/
|
||||||
|
public class MethodAssert extends AbstractObjectAssert<MethodAssert, Method> {
|
||||||
|
|
||||||
|
public MethodAssert(@Nullable Method actual) {
|
||||||
|
super(actual, MethodAssert.class);
|
||||||
|
as("Method %s", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the actual method has the given {@linkplain Method#getName()
|
||||||
|
* name}.
|
||||||
|
* @param name the expected method name
|
||||||
|
*/
|
||||||
|
public MethodAssert hasName(String name) {
|
||||||
|
isNotNull();
|
||||||
|
Assertions.assertThat(this.actual.getName()).as("Method name").isEqualTo(name);
|
||||||
|
return this.myself;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the actual method is declared in the given {@code type}.
|
||||||
|
* @param type the expected declaring class
|
||||||
|
*/
|
||||||
|
public MethodAssert hasDeclaringClass(Class<?> type) {
|
||||||
|
isNotNull();
|
||||||
|
Assertions.assertThat(this.actual.getDeclaringClass())
|
||||||
|
.as("Method declaring class").isEqualTo(type);
|
||||||
|
return this.myself;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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
|
||||||
|
*
|
||||||
|
* https://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.test.web.servlet.assertj;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.assertj.core.api.AbstractObjectAssert;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
|
||||||
|
import org.springframework.cglib.core.internal.Function;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.test.util.MethodAssert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to
|
||||||
|
* a handler or handler method.
|
||||||
|
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.2
|
||||||
|
*/
|
||||||
|
public class HandlerResultAssert extends AbstractObjectAssert<HandlerResultAssert, Object> {
|
||||||
|
|
||||||
|
public HandlerResultAssert(@Nullable Object actual) {
|
||||||
|
super(actual, HandlerResultAssert.class);
|
||||||
|
as("Handler result");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@linkplain MethodAssert assertion} object that uses
|
||||||
|
* the {@link Method} that handles the request as the object to test.
|
||||||
|
* Verify first that the handler is a {@linkplain #isMethodHandler() method
|
||||||
|
* handler}.
|
||||||
|
* Example: <pre><code class='java'>
|
||||||
|
* // Check that a GET to "/greet" is invoked on a "handleGreet" method name
|
||||||
|
* assertThat(mvc.perform(get("/greet")).handler().method().hasName("sayGreet");
|
||||||
|
* </code></pre>
|
||||||
|
*/
|
||||||
|
public MethodAssert method() {
|
||||||
|
return new MethodAssert(getHandlerMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the handler is managed by a method invocation, typically on
|
||||||
|
* a controller.
|
||||||
|
*/
|
||||||
|
public HandlerResultAssert isMethodHandler() {
|
||||||
|
return isNotNull().isInstanceOf(HandlerMethod.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the handler is managed by the given {@code handlerMethod}.
|
||||||
|
* This creates a "mock" for the given {@code controllerType} and record the
|
||||||
|
* method invocation in the {@code handlerMethod}. The arguments used by the
|
||||||
|
* target method invocation can be {@code null} as the purpose of the mock
|
||||||
|
* is to identify the method that was invoked.
|
||||||
|
* Example: <pre><code class='java'>
|
||||||
|
* // If the method has a return type, you can return the result of the invocation
|
||||||
|
* assertThat(mvc.perform(get("/greet")).handler().isInvokedOn(
|
||||||
|
* GreetController.class, controller -> controller.sayGreet());
|
||||||
|
* // If the method has a void return type, the controller should be returned
|
||||||
|
* assertThat(mvc.perform(post("/persons/")).handler().isInvokedOn(
|
||||||
|
* PersonController.class, controller -> controller.createPerson(null, null));
|
||||||
|
* </code></pre>
|
||||||
|
* @param controllerType the controller to mock
|
||||||
|
* @param handlerMethod the method
|
||||||
|
*/
|
||||||
|
public <T> HandlerResultAssert isInvokedOn(Class<T> controllerType, Function<T, Object> handlerMethod) {
|
||||||
|
MethodAssert actual = method();
|
||||||
|
Object methodInvocationInfo = handlerMethod.apply(MvcUriComponentsBuilder.on(controllerType));
|
||||||
|
Assertions.assertThat(methodInvocationInfo)
|
||||||
|
.as("Method invocation on controller '%s'", controllerType.getSimpleName())
|
||||||
|
.isInstanceOfSatisfying(MethodInvocationInfo.class, mii ->
|
||||||
|
actual.isEqualTo(mii.getControllerMethod()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the handler is of the given {@code type}. For a controller
|
||||||
|
* method, this is the type of the controller.
|
||||||
|
* Example: <pre><code class='java'>
|
||||||
|
* // Check that a GET to "/greet" is managed by GreetController
|
||||||
|
* assertThat(mvc.perform(get("/greet")).handler().hasType(GreetController.class);
|
||||||
|
* </code></pre>
|
||||||
|
* @param type the expected type of the handler
|
||||||
|
*/
|
||||||
|
public HandlerResultAssert hasType(Class<?> type) {
|
||||||
|
isNotNull();
|
||||||
|
Class<?> actualType = this.actual.getClass();
|
||||||
|
if (this.actual instanceof HandlerMethod handlerMethod) {
|
||||||
|
actualType = handlerMethod.getBeanType();
|
||||||
|
}
|
||||||
|
Assertions.assertThat(ClassUtils.getUserClass(actualType)).as("Handler result type").isEqualTo(type);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getHandlerMethod() {
|
||||||
|
isMethodHandler(); // validate type
|
||||||
|
return ((HandlerMethod) this.actual).getMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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
|
||||||
|
*
|
||||||
|
* https://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.test.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link MethodAssert}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class MethodAssertTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isEqualTo() {
|
||||||
|
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
|
||||||
|
assertThat(method).isEqualTo(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasName() {
|
||||||
|
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasName("counter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasNameWithWrongName() {
|
||||||
|
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(method).hasName("invalid"))
|
||||||
|
.withMessageContainingAll("Method name", "counter", "invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasNameWithNullMethod() {
|
||||||
|
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod");
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(method).hasName("name"))
|
||||||
|
.withMessageContaining("Expecting actual not to be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasDeclaringClass() {
|
||||||
|
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasDeclaringClass(TestData.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void haDeclaringClassWithWrongClass() {
|
||||||
|
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(method).hasDeclaringClass(Method.class))
|
||||||
|
.withMessageContainingAll("Method declaring class",
|
||||||
|
TestData.class.getCanonicalName(), Method.class.getCanonicalName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasDeclaringClassWithNullMethod() {
|
||||||
|
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod");
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(method).hasDeclaringClass(TestData.class))
|
||||||
|
.withMessageContaining("Expecting actual not to be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MethodAssert assertThat(@Nullable Method method) {
|
||||||
|
return new MethodAssert(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
record TestData(String name, int counter) {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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
|
||||||
|
*
|
||||||
|
* https://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.test.web.servlet.assertj;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.assertj.core.api.AssertProvider;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link HandlerResultAssert}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class HandlerResultAssertTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasTypeUseController() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet")).hasType(TestController.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isMethodHandlerWithMethodHandler() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet")).isMethodHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isMethodHandlerWithServletHandler() {
|
||||||
|
AssertProvider<HandlerResultAssert> actual = handler(new DefaultServletHttpRequestHandler());
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(actual).isMethodHandler())
|
||||||
|
.withMessageContainingAll(DefaultServletHttpRequestHandler.class.getName(),
|
||||||
|
HandlerMethod.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void methodName() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet")).method().hasName("greet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void declaringClass() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet")).method().hasDeclaringClass(TestController.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void method() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet")).method().isEqualTo(
|
||||||
|
ReflectionUtils.findMethod(TestController.class, "greet"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void methodWithServletHandler() {
|
||||||
|
AssertProvider<HandlerResultAssert> actual = handler(new DefaultServletHttpRequestHandler());
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(actual).method())
|
||||||
|
.withMessageContainingAll(DefaultServletHttpRequestHandler.class.getName(),
|
||||||
|
HandlerMethod.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isInvokedOn() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "greet"))
|
||||||
|
.isInvokedOn(TestController.class, TestController::greet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isInvokedOnWithVoidMethod() {
|
||||||
|
assertThat(handlerMethod(new TestController(), "update"))
|
||||||
|
.isInvokedOn(TestController.class, controller -> {
|
||||||
|
controller.update();
|
||||||
|
return controller;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isInvokedOnWithWrongMethod() {
|
||||||
|
AssertProvider<HandlerResultAssert> actual = handlerMethod(new TestController(), "update");
|
||||||
|
assertThatExceptionOfType(AssertionError.class)
|
||||||
|
.isThrownBy(() -> assertThat(actual).isInvokedOn(TestController.class, TestController::greet))
|
||||||
|
.withMessageContainingAll(
|
||||||
|
method(TestController.class, "greet").toGenericString(),
|
||||||
|
method(TestController.class, "update").toGenericString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AssertProvider<HandlerResultAssert> handler(Object instance) {
|
||||||
|
return () -> new HandlerResultAssert(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AssertProvider<HandlerResultAssert> handlerMethod(Object instance, String name, Class<?>... parameterTypes) {
|
||||||
|
HandlerMethod handlerMethod = new HandlerMethod(instance, method(instance.getClass(), name, parameterTypes));
|
||||||
|
return () -> new HandlerResultAssert(handlerMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method method(Class<?> target, String name, Class<?>... parameterTypes) {
|
||||||
|
Method method = ReflectionUtils.findMethod(target, name, parameterTypes);
|
||||||
|
Assertions.assertThat(method).isNotNull();
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public static class TestController {
|
||||||
|
|
||||||
|
@GetMapping("/greet")
|
||||||
|
public ResponseEntity<String> greet() {
|
||||||
|
return ResponseEntity.ok().body("Hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update")
|
||||||
|
public void update() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user