Browse Source

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
pull/29766/head
Simon Baslé 3 years ago committed by GitHub
parent
commit
74f58198fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      spring-test/src/main/kotlin/org/springframework/test/web/servlet/MockMvcResultMatchersDsl.kt
  2. 29
      spring-test/src/main/kotlin/org/springframework/test/web/servlet/ResultActionsDsl.kt
  3. 36
      spring-test/src/test/kotlin/org/springframework/test/web/servlet/MockMvcExtensionsTests.kt

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

@ -145,4 +145,12 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result @@ -145,4 +145,12 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
fun match(matcher: ResultMatcher) {
actions.andExpect(matcher)
}
/**
* @since 6.0.4
* @see ResultActions.andExpectAll
*/
fun matchAll(vararg matchers: ResultMatcher) {
actions.andExpectAll(*matchers)
}
}

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

@ -19,6 +19,35 @@ class ResultActionsDsl internal constructor (private val actions: ResultActions, @@ -19,6 +19,35 @@ class ResultActionsDsl internal constructor (private val actions: ResultActions,
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.
* @see MockMvcResultHandlersDsl.handle

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

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.test.web.servlet
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatCode
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.hamcrest.CoreMatchers
import org.junit.jupiter.api.Test
@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus @@ -25,6 +26,7 @@ import org.springframework.http.HttpStatus
import org.springframework.http.MediaType.APPLICATION_ATOM_XML
import org.springframework.http.MediaType.APPLICATION_JSON
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.servlet.setup.MockMvcBuilders
import org.springframework.web.bind.annotation.GetMapping
@ -97,6 +99,24 @@ class MockMvcExtensionsTests { @@ -97,6 +99,24 @@ class MockMvcExtensionsTests {
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
fun get() {
mockMvc.get("/person/{name}", "Lee") {
@ -183,6 +203,22 @@ class MockMvcExtensionsTests { @@ -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
private class PersonController {

Loading…
Cancel
Save