Skip to content

Commit 74f5819

Browse files
authored
Add Kotlin DSL support for MockMVC andExpectAll (#29727)
As the DSL internally calls `ResultActions.andExpect`, this is done with a trick where a synthetic `ResultActions` is provided at top level which stores each `ResultMatcher` in a mutable list. Once the DSL usage is done, the top level DSL `andExpectAll` turns that list into a `vararg` passed down to the actual `actions.andExpectAll`. Closes gh-27317
1 parent 42b1659 commit 74f5819

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,12 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
145145
fun match(matcher: ResultMatcher) {
146146
actions.andExpect(matcher)
147147
}
148+
149+
/**
150+
* @since 6.0.4
151+
* @see ResultActions.andExpectAll
152+
*/
153+
fun matchAll(vararg matchers: ResultMatcher) {
154+
actions.andExpectAll(*matchers)
155+
}
148156
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,35 @@ class ResultActionsDsl internal constructor (private val actions: ResultActions,
1919
return this
2020
}
2121

22+
23+
/**
24+
* Provide access to [MockMvcResultMatchersDsl] Kotlin DSL.
25+
* @since 6.0.4
26+
* @see MockMvcResultMatchersDsl.matchAll
27+
*/
28+
fun andExpectAll(dsl: MockMvcResultMatchersDsl.() -> Unit): ResultActionsDsl {
29+
val softMatchers = mutableListOf<ResultMatcher>()
30+
val softActions = object : ResultActions {
31+
override fun andExpect(matcher: ResultMatcher): ResultActions {
32+
softMatchers.add(matcher)
33+
return this
34+
}
35+
36+
override fun andDo(handler: ResultHandler): ResultActions {
37+
throw UnsupportedOperationException("andDo should not be part of andExpectAll DSL calls")
38+
}
39+
40+
override fun andReturn(): MvcResult {
41+
throw UnsupportedOperationException("andReturn should not be part of andExpectAll DSL calls")
42+
}
43+
44+
}
45+
// the use of softActions as the matchers DSL actions parameter will store ResultMatchers in list
46+
MockMvcResultMatchersDsl(softActions).dsl()
47+
actions.andExpectAll(*softMatchers.toTypedArray())
48+
return this;
49+
}
50+
2251
/**
2352
* Provide access to [MockMvcResultHandlersDsl] Kotlin DSL.
2453
* @see MockMvcResultHandlersDsl.handle

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.test.web.servlet
1818

1919
import org.assertj.core.api.Assertions.assertThat
20+
import org.assertj.core.api.Assertions.assertThatCode
2021
import org.assertj.core.api.Assertions.assertThatExceptionOfType
2122
import org.hamcrest.CoreMatchers
2223
import org.junit.jupiter.api.Test
@@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus
2526
import org.springframework.http.MediaType.APPLICATION_ATOM_XML
2627
import org.springframework.http.MediaType.APPLICATION_JSON
2728
import org.springframework.http.MediaType.APPLICATION_XML
29+
import org.springframework.http.MediaType.TEXT_PLAIN
2830
import org.springframework.test.web.Person
2931
import org.springframework.test.web.servlet.setup.MockMvcBuilders
3032
import org.springframework.web.bind.annotation.GetMapping
@@ -97,6 +99,24 @@ class MockMvcExtensionsTests {
9799
assertThat(handlerInvoked).isTrue()
98100
}
99101

102+
@Test
103+
fun `request with two custom matchers and matchAll`() {
104+
var matcher1Invoked = false
105+
var matcher2Invoked = false
106+
val matcher1 = ResultMatcher { matcher1Invoked = true; throw AssertionError("expected") }
107+
val matcher2 = ResultMatcher { matcher2Invoked = true }
108+
assertThatExceptionOfType(AssertionError::class.java).isThrownBy {
109+
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee")
110+
.andExpect {
111+
matchAll(matcher1, matcher2)
112+
}
113+
}
114+
.withMessage("expected")
115+
116+
assertThat(matcher1Invoked).describedAs("matcher1").isTrue()
117+
assertThat(matcher2Invoked).describedAs("matcher2").isTrue()
118+
}
119+
100120
@Test
101121
fun get() {
102122
mockMvc.get("/person/{name}", "Lee") {
@@ -183,6 +203,22 @@ class MockMvcExtensionsTests {
183203
}
184204
}
185205

206+
@Test
207+
fun `andExpectAll reports multiple assertion errors`() {
208+
assertThatCode {
209+
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee") {
210+
accept = APPLICATION_JSON
211+
}.andExpectAll {
212+
status { is4xxClientError() }
213+
content { contentType(TEXT_PLAIN) }
214+
jsonPath("$.name") { value("Lee") }
215+
}
216+
}
217+
.hasMessage("Multiple Exceptions (2):\n" +
218+
"Range for response status value 200 expected:<CLIENT_ERROR> but was:<SUCCESSFUL>\n" +
219+
"Content type expected:<text/plain> but was:<application/json>")
220+
}
221+
186222

187223
@RestController
188224
private class PersonController {

0 commit comments

Comments
 (0)