Skip to content

Commit b46e528

Browse files
committed
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
1 parent e97ae43 commit b46e528

File tree

4 files changed

+416
-0
lines changed

4 files changed

+416
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.util;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.assertj.core.api.AbstractObjectAssert;
22+
import org.assertj.core.api.Assertions;
23+
24+
import org.springframework.lang.Nullable;
25+
26+
/**
27+
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
28+
* to a {@link Method}.
29+
*
30+
* @author Stephane Nicoll
31+
* @since 6.2
32+
*/
33+
public class MethodAssert extends AbstractObjectAssert<MethodAssert, Method> {
34+
35+
public MethodAssert(@Nullable Method actual) {
36+
super(actual, MethodAssert.class);
37+
as("Method %s", actual);
38+
}
39+
40+
/**
41+
* Verify that the actual method has the given {@linkplain Method#getName()
42+
* name}.
43+
* @param name the expected method name
44+
*/
45+
public MethodAssert hasName(String name) {
46+
isNotNull();
47+
Assertions.assertThat(this.actual.getName()).as("Method name").isEqualTo(name);
48+
return this.myself;
49+
}
50+
51+
/**
52+
* Verify that the actual method is declared in the given {@code type}.
53+
* @param type the expected declaring class
54+
*/
55+
public MethodAssert hasDeclaringClass(Class<?> type) {
56+
isNotNull();
57+
Assertions.assertThat(this.actual.getDeclaringClass())
58+
.as("Method declaring class").isEqualTo(type);
59+
return this.myself;
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.web.servlet.assertj;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.assertj.core.api.AbstractObjectAssert;
22+
import org.assertj.core.api.Assertions;
23+
24+
import org.springframework.cglib.core.internal.Function;
25+
import org.springframework.lang.Nullable;
26+
import org.springframework.test.util.MethodAssert;
27+
import org.springframework.util.ClassUtils;
28+
import org.springframework.web.method.HandlerMethod;
29+
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
30+
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo;
31+
32+
/**
33+
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to
34+
* a handler or handler method.
35+
36+
* @author Stephane Nicoll
37+
* @since 6.2
38+
*/
39+
public class HandlerResultAssert extends AbstractObjectAssert<HandlerResultAssert, Object> {
40+
41+
public HandlerResultAssert(@Nullable Object actual) {
42+
super(actual, HandlerResultAssert.class);
43+
as("Handler result");
44+
}
45+
46+
/**
47+
* Return a new {@linkplain MethodAssert assertion} object that uses
48+
* the {@link Method} that handles the request as the object to test.
49+
* Verify first that the handler is a {@linkplain #isMethodHandler() method
50+
* handler}.
51+
* Example: <pre><code class='java'>
52+
* // Check that a GET to "/greet" is invoked on a "handleGreet" method name
53+
* assertThat(mvc.perform(get("/greet")).handler().method().hasName("sayGreet");
54+
* </code></pre>
55+
*/
56+
public MethodAssert method() {
57+
return new MethodAssert(getHandlerMethod());
58+
}
59+
60+
/**
61+
* Verify that the handler is managed by a method invocation, typically on
62+
* a controller.
63+
*/
64+
public HandlerResultAssert isMethodHandler() {
65+
return isNotNull().isInstanceOf(HandlerMethod.class);
66+
}
67+
68+
/**
69+
* Verify that the handler is managed by the given {@code handlerMethod}.
70+
* This creates a "mock" for the given {@code controllerType} and record the
71+
* method invocation in the {@code handlerMethod}. The arguments used by the
72+
* target method invocation can be {@code null} as the purpose of the mock
73+
* is to identify the method that was invoked.
74+
* Example: <pre><code class='java'>
75+
* // If the method has a return type, you can return the result of the invocation
76+
* assertThat(mvc.perform(get("/greet")).handler().isInvokedOn(
77+
* GreetController.class, controller -> controller.sayGreet());
78+
* // If the method has a void return type, the controller should be returned
79+
* assertThat(mvc.perform(post("/persons/")).handler().isInvokedOn(
80+
* PersonController.class, controller -> controller.createPerson(null, null));
81+
* </code></pre>
82+
* @param controllerType the controller to mock
83+
* @param handlerMethod the method
84+
*/
85+
public <T> HandlerResultAssert isInvokedOn(Class<T> controllerType, Function<T, Object> handlerMethod) {
86+
MethodAssert actual = method();
87+
Object methodInvocationInfo = handlerMethod.apply(MvcUriComponentsBuilder.on(controllerType));
88+
Assertions.assertThat(methodInvocationInfo)
89+
.as("Method invocation on controller '%s'", controllerType.getSimpleName())
90+
.isInstanceOfSatisfying(MethodInvocationInfo.class, mii ->
91+
actual.isEqualTo(mii.getControllerMethod()));
92+
return this;
93+
}
94+
95+
/**
96+
* Verify that the handler is of the given {@code type}. For a controller
97+
* method, this is the type of the controller.
98+
* Example: <pre><code class='java'>
99+
* // Check that a GET to "/greet" is managed by GreetController
100+
* assertThat(mvc.perform(get("/greet")).handler().hasType(GreetController.class);
101+
* </code></pre>
102+
* @param type the expected type of the handler
103+
*/
104+
public HandlerResultAssert hasType(Class<?> type) {
105+
isNotNull();
106+
Class<?> actualType = this.actual.getClass();
107+
if (this.actual instanceof HandlerMethod handlerMethod) {
108+
actualType = handlerMethod.getBeanType();
109+
}
110+
Assertions.assertThat(ClassUtils.getUserClass(actualType)).as("Handler result type").isEqualTo(type);
111+
return this;
112+
}
113+
114+
private Method getHandlerMethod() {
115+
isMethodHandler(); // validate type
116+
return ((HandlerMethod) this.actual).getMethod();
117+
}
118+
119+
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.util;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.util.ReflectionUtils;
25+
26+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
27+
28+
/**
29+
* Tests for {@link MethodAssert}.
30+
*
31+
* @author Stephane Nicoll
32+
*/
33+
class MethodAssertTests {
34+
35+
@Test
36+
void isEqualTo() {
37+
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
38+
assertThat(method).isEqualTo(method);
39+
}
40+
41+
@Test
42+
void hasName() {
43+
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasName("counter");
44+
}
45+
46+
@Test
47+
void hasNameWithWrongName() {
48+
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
49+
assertThatExceptionOfType(AssertionError.class)
50+
.isThrownBy(() -> assertThat(method).hasName("invalid"))
51+
.withMessageContainingAll("Method name", "counter", "invalid");
52+
}
53+
54+
@Test
55+
void hasNameWithNullMethod() {
56+
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod");
57+
assertThatExceptionOfType(AssertionError.class)
58+
.isThrownBy(() -> assertThat(method).hasName("name"))
59+
.withMessageContaining("Expecting actual not to be null");
60+
}
61+
62+
@Test
63+
void hasDeclaringClass() {
64+
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasDeclaringClass(TestData.class);
65+
}
66+
67+
@Test
68+
void haDeclaringClassWithWrongClass() {
69+
Method method = ReflectionUtils.findMethod(TestData.class, "counter");
70+
assertThatExceptionOfType(AssertionError.class)
71+
.isThrownBy(() -> assertThat(method).hasDeclaringClass(Method.class))
72+
.withMessageContainingAll("Method declaring class",
73+
TestData.class.getCanonicalName(), Method.class.getCanonicalName());
74+
}
75+
76+
@Test
77+
void hasDeclaringClassWithNullMethod() {
78+
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod");
79+
assertThatExceptionOfType(AssertionError.class)
80+
.isThrownBy(() -> assertThat(method).hasDeclaringClass(TestData.class))
81+
.withMessageContaining("Expecting actual not to be null");
82+
}
83+
84+
85+
private MethodAssert assertThat(@Nullable Method method) {
86+
return new MethodAssert(method);
87+
}
88+
89+
90+
record TestData(String name, int counter) {}
91+
92+
}

0 commit comments

Comments
 (0)