Skip to content

Commit d04c5f8

Browse files
committed
Support multiple matchers in MockMvc Kotlin DSL
Previous incarnation of MockMvc Kotlin DSL tried to reuse directly Java APIs like ModelResultMatchers or StatusResultMatchers, but when using multiple matchers in DSL blocks like model { } or status { }, only the last statement was taken in account which was very confusing. This refactoring provides dedicated Kotlin DSLs for matchers. The main API breaking changes is that functions like isOk() need to be invoked with the parenthesis, isOk is not supported anymore (on purpose). Closes gh-24103
1 parent 41247d4 commit d04c5f8

16 files changed

+1483
-35
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/result/FlashAttributeResultMatchers.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.hamcrest.Matcher;
2020

21+
import org.springframework.lang.Nullable;
2122
import org.springframework.test.web.servlet.ResultMatcher;
2223

2324
import static org.hamcrest.MatcherAssert.assertThat;
@@ -54,7 +55,7 @@ public <T> ResultMatcher attribute(String name, Matcher<? super T> matcher) {
5455
/**
5556
* Assert a flash attribute's value.
5657
*/
57-
public ResultMatcher attribute(String name, Object value) {
58+
public ResultMatcher attribute(String name, @Nullable Object value) {
5859
return result -> assertEquals("Flash attribute '" + name + "'", value, result.getFlashMap().get(name));
5960
}
6061

spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public <T> ResultMatcher value(Matcher<? super T> matcher, Class<T> targetType)
107107
* @see #value(Matcher)
108108
* @see #value(Matcher, Class)
109109
*/
110-
public ResultMatcher value(Object expectedValue) {
110+
public ResultMatcher value(@Nullable Object expectedValue) {
111111
return result -> this.jsonPathHelper.assertValue(getContent(result), expectedValue);
112112
}
113113

spring-test/src/main/java/org/springframework/test/web/servlet/result/ModelResultMatchers.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.hamcrest.Matcher;
2020

21+
import org.springframework.lang.Nullable;
2122
import org.springframework.test.web.servlet.MvcResult;
2223
import org.springframework.test.web.servlet.ResultMatcher;
2324
import org.springframework.ui.ModelMap;
@@ -67,7 +68,7 @@ public <T> ResultMatcher attribute(String name, Matcher<? super T> matcher) {
6768
/**
6869
* Assert a model attribute value.
6970
*/
70-
public ResultMatcher attribute(String name, Object value) {
71+
public ResultMatcher attribute(String name, @Nullable Object value) {
7172
return result -> {
7273
ModelAndView mav = getModelAndView(result);
7374
assertEquals("Model attribute '" + name + "'", value, mav.getModel().get(name));

spring-test/src/main/java/org/springframework/test/web/servlet/result/RequestResultMatchers.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.hamcrest.Matcher;
2525

26+
import org.springframework.lang.Nullable;
2627
import org.springframework.mock.web.MockHttpServletRequest;
2728
import org.springframework.test.web.servlet.ResultMatcher;
2829
import org.springframework.util.Assert;
@@ -98,7 +99,7 @@ public <T> ResultMatcher asyncResult(Matcher<? super T> matcher) {
9899
* or {@link WebAsyncTask}. The value matched is the value returned from the
99100
* {@code Callable} or the exception raised.
100101
*/
101-
public ResultMatcher asyncResult(Object expectedResult) {
102+
public ResultMatcher asyncResult(@Nullable Object expectedResult) {
102103
return result -> {
103104
HttpServletRequest request = result.getRequest();
104105
assertAsyncStarted(request);
@@ -120,7 +121,7 @@ public <T> ResultMatcher attribute(String name, Matcher<? super T> matcher) {
120121
/**
121122
* Assert a request attribute value.
122123
*/
123-
public ResultMatcher attribute(String name, Object expectedValue) {
124+
public ResultMatcher attribute(String name, @Nullable Object expectedValue) {
124125
return result ->
125126
assertEquals("Request attribute '" + name + "'", expectedValue, result.getRequest().getAttribute(name));
126127
}
@@ -141,7 +142,7 @@ public <T> ResultMatcher sessionAttribute(String name, Matcher<? super T> matche
141142
/**
142143
* Assert a session attribute value.
143144
*/
144-
public ResultMatcher sessionAttribute(String name, Object value) {
145+
public ResultMatcher sessionAttribute(String name, @Nullable Object value) {
145146
return result -> {
146147
HttpSession session = result.getRequest().getSession();
147148
Assert.state(session != null, "No HttpSession");

spring-test/src/main/kotlin/org/springframework/test/web/servlet/MockMvcResultMatchersDsl.kt

+20-21
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,29 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
3030
/**
3131
* @see MockMvcResultMatchers.request
3232
*/
33-
fun request(matcher: RequestResultMatchers.() -> ResultMatcher) {
34-
actions.andExpect(MockMvcResultMatchers.request().matcher())
33+
fun request(dsl: RequestResultMatchersDsl.() -> Unit) {
34+
RequestResultMatchersDsl(actions).dsl()
3535
}
3636

3737
/**
3838
* @see MockMvcResultMatchers.view
3939
*/
40-
fun view(matcher: ViewResultMatchers.() -> ResultMatcher) {
41-
actions.andExpect(MockMvcResultMatchers.view().matcher())
40+
fun view(dsl: ViewResultMatchersDsl.() -> Unit) {
41+
ViewResultMatchersDsl(actions).dsl()
4242
}
4343

4444
/**
4545
* @see MockMvcResultMatchers.model
4646
*/
47-
fun model(matcher: ModelResultMatchers.() -> ResultMatcher) {
48-
actions.andExpect(MockMvcResultMatchers.model().matcher())
47+
fun model(dsl: ModelResultMatchersDsl.() -> Unit) {
48+
ModelResultMatchersDsl(actions).dsl()
4949
}
5050

5151
/**
5252
* @see MockMvcResultMatchers.flash
5353
*/
54-
fun flash(matcher: FlashAttributeResultMatchers.() -> ResultMatcher) {
55-
actions.andExpect(MockMvcResultMatchers.flash().matcher())
54+
fun flash(dsl: FlashAttributeResultMatchersDsl.() -> Unit) {
55+
FlashAttributeResultMatchersDsl(actions).dsl()
5656
}
5757

5858
/**
@@ -93,22 +93,22 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
9393
/**
9494
* @see MockMvcResultMatchers.status
9595
*/
96-
fun status(matcher: StatusResultMatchers.() -> ResultMatcher) {
97-
actions.andExpect(MockMvcResultMatchers.status().matcher())
96+
fun status(dsl: StatusResultMatchersDsl.() -> Unit) {
97+
StatusResultMatchersDsl(actions).dsl()
9898
}
9999

100100
/**
101101
* @see MockMvcResultMatchers.header
102102
*/
103-
fun header(matcher: HeaderResultMatchers.() -> ResultMatcher) {
104-
actions.andExpect(MockMvcResultMatchers.header().matcher())
103+
fun header(dsl: HeaderResultMatchersDsl.() -> Unit) {
104+
HeaderResultMatchersDsl(actions).dsl()
105105
}
106106

107107
/**
108108
* @see MockMvcResultMatchers.content
109109
*/
110-
fun content(matcher: ContentResultMatchers.() -> ResultMatcher) {
111-
actions.andExpect(MockMvcResultMatchers.content().matcher())
110+
fun content(dsl: ContentResultMatchersDsl.() -> Unit) {
111+
ContentResultMatchersDsl(actions).dsl()
112112
}
113113

114114
/**
@@ -121,23 +121,22 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
121121
/**
122122
* @see MockMvcResultMatchers.jsonPath
123123
*/
124-
fun jsonPath(expression: String, vararg args: Any, block: JsonPathResultMatchers.() -> ResultMatcher) {
125-
actions.andExpect(MockMvcResultMatchers.jsonPath(expression, *args).block())
124+
fun jsonPath(expression: String, vararg args: Any?, dsl: JsonPathResultMatchersDsl.() -> Unit) {
125+
JsonPathResultMatchersDsl(actions, expression, *args).dsl()
126126
}
127127

128128
/**
129129
* @see MockMvcResultMatchers.xpath
130130
*/
131-
fun xpath(expression: String, vararg args: Any, namespaces: Map<String, String>? = null, xpathInit: XpathResultMatchers.() -> ResultMatcher) {
132-
actions.andExpect(MockMvcResultMatchers.xpath(expression, namespaces, args).xpathInit())
131+
fun xpath(expression: String, vararg args: Any?, namespaces: Map<String, String>? = null, dsl: XpathResultMatchersDsl.() -> Unit) {
132+
XpathResultMatchersDsl(actions, expression, namespaces, *args).dsl()
133133
}
134134

135135
/**
136136
* @see MockMvcResultMatchers.cookie
137137
*/
138-
fun cookie(cookieInit: CookieResultMatchers.() -> ResultMatcher) {
139-
val cookie = MockMvcResultMatchers.cookie().cookieInit()
140-
actions.andExpect(cookie)
138+
fun cookie(dsl: CookieResultMatchersDsl.() -> Unit) {
139+
CookieResultMatchersDsl(actions).dsl()
141140
}
142141

143142
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2002-2020 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.result
18+
19+
import org.hamcrest.Matcher
20+
import org.springframework.http.MediaType
21+
import org.springframework.test.web.servlet.ResultActions
22+
import org.w3c.dom.Node
23+
import javax.xml.transform.Source
24+
25+
/**
26+
* Provide a [ContentResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
27+
*
28+
* @author Sebastien Deleuze
29+
* @since 5.3
30+
*/
31+
class ContentResultMatchersDsl internal constructor (private val actions: ResultActions) {
32+
33+
private val matchers = MockMvcResultMatchers.content()
34+
35+
/**
36+
* @see ContentResultMatchers.contentType
37+
*/
38+
fun contentType(contentType: String) {
39+
actions.andExpect(matchers.contentType(contentType))
40+
}
41+
42+
/**
43+
* @see ContentResultMatchers.contentType
44+
*/
45+
fun contentType(contentType: MediaType) {
46+
actions.andExpect(matchers.contentType(contentType))
47+
}
48+
49+
/**
50+
* @see ContentResultMatchers.contentTypeCompatibleWith
51+
*/
52+
fun contentTypeCompatibleWith(contentType: String) {
53+
actions.andExpect(matchers.contentTypeCompatibleWith(contentType))
54+
}
55+
56+
/**
57+
* @see ContentResultMatchers.contentTypeCompatibleWith
58+
*/
59+
fun contentTypeCompatibleWith(contentType: MediaType) {
60+
actions.andExpect(matchers.contentTypeCompatibleWith(contentType))
61+
}
62+
63+
/**
64+
* @see ContentResultMatchers.encoding
65+
*/
66+
fun encoding(contentType: String) {
67+
actions.andExpect(matchers.encoding(contentType))
68+
}
69+
70+
/**
71+
* @see ContentResultMatchers.string
72+
*/
73+
fun string(matcher: Matcher<String>) {
74+
actions.andExpect(matchers.string(matcher))
75+
}
76+
77+
/**
78+
* @see ContentResultMatchers.string
79+
*/
80+
fun string(expectedContent: String) {
81+
actions.andExpect(matchers.string(expectedContent))
82+
}
83+
84+
/**
85+
* @see ContentResultMatchers.bytes
86+
*/
87+
fun bytes(expectedContent: ByteArray) {
88+
actions.andExpect(matchers.bytes(expectedContent))
89+
}
90+
91+
/**
92+
* @see ContentResultMatchers.xml
93+
*/
94+
fun xml(xmlContent: String) {
95+
actions.andExpect(matchers.xml(xmlContent))
96+
}
97+
98+
/**
99+
* @see ContentResultMatchers.node
100+
*/
101+
fun node(matcher: Matcher<Node>) {
102+
actions.andExpect(matchers.node(matcher))
103+
}
104+
105+
/**
106+
* @see ContentResultMatchers.source
107+
*/
108+
fun source(matcher: Matcher<Source>) {
109+
actions.andExpect(matchers.source(matcher))
110+
}
111+
112+
/**
113+
* @see ContentResultMatchers.json
114+
*/
115+
fun json(jsonContent: String, strict: Boolean = false) {
116+
actions.andExpect(matchers.json(jsonContent, strict))
117+
}
118+
}

0 commit comments

Comments
 (0)