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
This commit is contained in:
@@ -145,4 +145,12 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
|
|||||||
fun match(matcher: ResultMatcher) {
|
fun match(matcher: ResultMatcher) {
|
||||||
actions.andExpect(matcher)
|
actions.andExpect(matcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 6.0.4
|
||||||
|
* @see ResultActions.andExpectAll
|
||||||
|
*/
|
||||||
|
fun matchAll(vararg matchers: ResultMatcher) {
|
||||||
|
actions.andExpectAll(*matchers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,35 @@ class ResultActionsDsl internal constructor (private val actions: ResultActions,
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide access to [MockMvcResultMatchersDsl] Kotlin DSL.
|
||||||
|
* @since 6.0.4
|
||||||
|
* @see MockMvcResultMatchersDsl.matchAll
|
||||||
|
*/
|
||||||
|
fun andExpectAll(dsl: MockMvcResultMatchersDsl.() -> Unit): ResultActionsDsl {
|
||||||
|
val softMatchers = mutableListOf<ResultMatcher>()
|
||||||
|
val softActions = object : ResultActions {
|
||||||
|
override fun andExpect(matcher: ResultMatcher): ResultActions {
|
||||||
|
softMatchers.add(matcher)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun andDo(handler: ResultHandler): ResultActions {
|
||||||
|
throw UnsupportedOperationException("andDo should not be part of andExpectAll DSL calls")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun andReturn(): MvcResult {
|
||||||
|
throw UnsupportedOperationException("andReturn should not be part of andExpectAll DSL calls")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// the use of softActions as the matchers DSL actions parameter will store ResultMatchers in list
|
||||||
|
MockMvcResultMatchersDsl(softActions).dsl()
|
||||||
|
actions.andExpectAll(*softMatchers.toTypedArray())
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide access to [MockMvcResultHandlersDsl] Kotlin DSL.
|
* Provide access to [MockMvcResultHandlersDsl] Kotlin DSL.
|
||||||
* @see MockMvcResultHandlersDsl.handle
|
* @see MockMvcResultHandlersDsl.handle
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.test.web.servlet
|
package org.springframework.test.web.servlet
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatCode
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus
|
|||||||
import org.springframework.http.MediaType.APPLICATION_ATOM_XML
|
import org.springframework.http.MediaType.APPLICATION_ATOM_XML
|
||||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||||
import org.springframework.http.MediaType.APPLICATION_XML
|
import org.springframework.http.MediaType.APPLICATION_XML
|
||||||
|
import org.springframework.http.MediaType.TEXT_PLAIN
|
||||||
import org.springframework.test.web.Person
|
import org.springframework.test.web.Person
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
@@ -97,6 +99,24 @@ class MockMvcExtensionsTests {
|
|||||||
assertThat(handlerInvoked).isTrue()
|
assertThat(handlerInvoked).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request with two custom matchers and matchAll`() {
|
||||||
|
var matcher1Invoked = false
|
||||||
|
var matcher2Invoked = false
|
||||||
|
val matcher1 = ResultMatcher { matcher1Invoked = true; throw AssertionError("expected") }
|
||||||
|
val matcher2 = ResultMatcher { matcher2Invoked = true }
|
||||||
|
assertThatExceptionOfType(AssertionError::class.java).isThrownBy {
|
||||||
|
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee")
|
||||||
|
.andExpect {
|
||||||
|
matchAll(matcher1, matcher2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.withMessage("expected")
|
||||||
|
|
||||||
|
assertThat(matcher1Invoked).describedAs("matcher1").isTrue()
|
||||||
|
assertThat(matcher2Invoked).describedAs("matcher2").isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun get() {
|
fun get() {
|
||||||
mockMvc.get("/person/{name}", "Lee") {
|
mockMvc.get("/person/{name}", "Lee") {
|
||||||
@@ -183,6 +203,22 @@ class MockMvcExtensionsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `andExpectAll reports multiple assertion errors`() {
|
||||||
|
assertThatCode {
|
||||||
|
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee") {
|
||||||
|
accept = APPLICATION_JSON
|
||||||
|
}.andExpectAll {
|
||||||
|
status { is4xxClientError() }
|
||||||
|
content { contentType(TEXT_PLAIN) }
|
||||||
|
jsonPath("$.name") { value("Lee") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hasMessage("Multiple Exceptions (2):\n" +
|
||||||
|
"Range for response status value 200 expected:<CLIENT_ERROR> but was:<SUCCESSFUL>\n" +
|
||||||
|
"Content type expected:<text/plain> but was:<application/json>")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
private class PersonController {
|
private class PersonController {
|
||||||
|
|||||||
Reference in New Issue
Block a user