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
This commit is contained in:
Sébastien Deleuze 2020-10-26 18:13:39 +01:00
parent 41247d49ba
commit d04c5f8b2c
16 changed files with 1483 additions and 35 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.result;
import org.hamcrest.Matcher;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.hamcrest.MatcherAssert.assertThat;
@ -54,7 +55,7 @@ public class FlashAttributeResultMatchers {
/**
* Assert a flash attribute's value.
*/
public ResultMatcher attribute(String name, Object value) {
public ResultMatcher attribute(String name, @Nullable Object value) {
return result -> assertEquals("Flash attribute '" + name + "'", value, result.getFlashMap().get(name));
}

View File

@ -107,7 +107,7 @@ public class JsonPathResultMatchers {
* @see #value(Matcher)
* @see #value(Matcher, Class)
*/
public ResultMatcher value(Object expectedValue) {
public ResultMatcher value(@Nullable Object expectedValue) {
return result -> this.jsonPathHelper.assertValue(getContent(result), expectedValue);
}

View File

@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.result;
import org.hamcrest.Matcher;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.ui.ModelMap;
@ -67,7 +68,7 @@ public class ModelResultMatchers {
/**
* Assert a model attribute value.
*/
public ResultMatcher attribute(String name, Object value) {
public ResultMatcher attribute(String name, @Nullable Object value) {
return result -> {
ModelAndView mav = getModelAndView(result);
assertEquals("Model attribute '" + name + "'", value, mav.getModel().get(name));

View File

@ -23,6 +23,7 @@ import javax.servlet.http.HttpSession;
import org.hamcrest.Matcher;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.Assert;
@ -98,7 +99,7 @@ public class RequestResultMatchers {
* or {@link WebAsyncTask}. The value matched is the value returned from the
* {@code Callable} or the exception raised.
*/
public ResultMatcher asyncResult(Object expectedResult) {
public ResultMatcher asyncResult(@Nullable Object expectedResult) {
return result -> {
HttpServletRequest request = result.getRequest();
assertAsyncStarted(request);
@ -120,7 +121,7 @@ public class RequestResultMatchers {
/**
* Assert a request attribute value.
*/
public ResultMatcher attribute(String name, Object expectedValue) {
public ResultMatcher attribute(String name, @Nullable Object expectedValue) {
return result ->
assertEquals("Request attribute '" + name + "'", expectedValue, result.getRequest().getAttribute(name));
}
@ -141,7 +142,7 @@ public class RequestResultMatchers {
/**
* Assert a session attribute value.
*/
public ResultMatcher sessionAttribute(String name, Object value) {
public ResultMatcher sessionAttribute(String name, @Nullable Object value) {
return result -> {
HttpSession session = result.getRequest().getSession();
Assert.state(session != null, "No HttpSession");

View File

@ -30,29 +30,29 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
/**
* @see MockMvcResultMatchers.request
*/
fun request(matcher: RequestResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.request().matcher())
fun request(dsl: RequestResultMatchersDsl.() -> Unit) {
RequestResultMatchersDsl(actions).dsl()
}
/**
* @see MockMvcResultMatchers.view
*/
fun view(matcher: ViewResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.view().matcher())
fun view(dsl: ViewResultMatchersDsl.() -> Unit) {
ViewResultMatchersDsl(actions).dsl()
}
/**
* @see MockMvcResultMatchers.model
*/
fun model(matcher: ModelResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.model().matcher())
fun model(dsl: ModelResultMatchersDsl.() -> Unit) {
ModelResultMatchersDsl(actions).dsl()
}
/**
* @see MockMvcResultMatchers.flash
*/
fun flash(matcher: FlashAttributeResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.flash().matcher())
fun flash(dsl: FlashAttributeResultMatchersDsl.() -> Unit) {
FlashAttributeResultMatchersDsl(actions).dsl()
}
/**
@ -93,22 +93,22 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
/**
* @see MockMvcResultMatchers.status
*/
fun status(matcher: StatusResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.status().matcher())
fun status(dsl: StatusResultMatchersDsl.() -> Unit) {
StatusResultMatchersDsl(actions).dsl()
}
/**
* @see MockMvcResultMatchers.header
*/
fun header(matcher: HeaderResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.header().matcher())
fun header(dsl: HeaderResultMatchersDsl.() -> Unit) {
HeaderResultMatchersDsl(actions).dsl()
}
/**
* @see MockMvcResultMatchers.content
*/
fun content(matcher: ContentResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.content().matcher())
fun content(dsl: ContentResultMatchersDsl.() -> Unit) {
ContentResultMatchersDsl(actions).dsl()
}
/**
@ -121,23 +121,22 @@ class MockMvcResultMatchersDsl internal constructor (private val actions: Result
/**
* @see MockMvcResultMatchers.jsonPath
*/
fun jsonPath(expression: String, vararg args: Any, block: JsonPathResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.jsonPath(expression, *args).block())
fun jsonPath(expression: String, vararg args: Any?, dsl: JsonPathResultMatchersDsl.() -> Unit) {
JsonPathResultMatchersDsl(actions, expression, *args).dsl()
}
/**
* @see MockMvcResultMatchers.xpath
*/
fun xpath(expression: String, vararg args: Any, namespaces: Map<String, String>? = null, xpathInit: XpathResultMatchers.() -> ResultMatcher) {
actions.andExpect(MockMvcResultMatchers.xpath(expression, namespaces, args).xpathInit())
fun xpath(expression: String, vararg args: Any?, namespaces: Map<String, String>? = null, dsl: XpathResultMatchersDsl.() -> Unit) {
XpathResultMatchersDsl(actions, expression, namespaces, *args).dsl()
}
/**
* @see MockMvcResultMatchers.cookie
*/
fun cookie(cookieInit: CookieResultMatchers.() -> ResultMatcher) {
val cookie = MockMvcResultMatchers.cookie().cookieInit()
actions.andExpect(cookie)
fun cookie(dsl: CookieResultMatchersDsl.() -> Unit) {
CookieResultMatchersDsl(actions).dsl()
}
/**

View File

@ -0,0 +1,118 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.ResultActions
import org.w3c.dom.Node
import javax.xml.transform.Source
/**
* Provide a [ContentResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class ContentResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.content()
/**
* @see ContentResultMatchers.contentType
*/
fun contentType(contentType: String) {
actions.andExpect(matchers.contentType(contentType))
}
/**
* @see ContentResultMatchers.contentType
*/
fun contentType(contentType: MediaType) {
actions.andExpect(matchers.contentType(contentType))
}
/**
* @see ContentResultMatchers.contentTypeCompatibleWith
*/
fun contentTypeCompatibleWith(contentType: String) {
actions.andExpect(matchers.contentTypeCompatibleWith(contentType))
}
/**
* @see ContentResultMatchers.contentTypeCompatibleWith
*/
fun contentTypeCompatibleWith(contentType: MediaType) {
actions.andExpect(matchers.contentTypeCompatibleWith(contentType))
}
/**
* @see ContentResultMatchers.encoding
*/
fun encoding(contentType: String) {
actions.andExpect(matchers.encoding(contentType))
}
/**
* @see ContentResultMatchers.string
*/
fun string(matcher: Matcher<String>) {
actions.andExpect(matchers.string(matcher))
}
/**
* @see ContentResultMatchers.string
*/
fun string(expectedContent: String) {
actions.andExpect(matchers.string(expectedContent))
}
/**
* @see ContentResultMatchers.bytes
*/
fun bytes(expectedContent: ByteArray) {
actions.andExpect(matchers.bytes(expectedContent))
}
/**
* @see ContentResultMatchers.xml
*/
fun xml(xmlContent: String) {
actions.andExpect(matchers.xml(xmlContent))
}
/**
* @see ContentResultMatchers.node
*/
fun node(matcher: Matcher<Node>) {
actions.andExpect(matchers.node(matcher))
}
/**
* @see ContentResultMatchers.source
*/
fun source(matcher: Matcher<Source>) {
actions.andExpect(matchers.source(matcher))
}
/**
* @see ContentResultMatchers.json
*/
fun json(jsonContent: String, strict: Boolean = false) {
actions.andExpect(matchers.json(jsonContent, strict))
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [CookieResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class CookieResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.cookie()
/**
* @see CookieResultMatchers.value
*/
fun value(name: String, matcher: Matcher<String>) {
actions.andExpect(matchers.value(name, matcher))
}
/**
* @see CookieResultMatchers.value
*/
fun value(name: String, expectedValue: String) {
actions.andExpect(matchers.value(name, expectedValue))
}
/**
* @see CookieResultMatchers.exists
*/
fun exists(name: String) {
actions.andExpect(matchers.exists(name))
}
/**
* @see CookieResultMatchers.doesNotExist
*/
fun doesNotExist(name: String) {
actions.andExpect(matchers.doesNotExist(name))
}
/**
* @see CookieResultMatchers.maxAge
*/
fun maxAge(name: String, matcher: Matcher<Int>) {
actions.andExpect(matchers.maxAge(name, matcher))
}
/**
* @see CookieResultMatchers.maxAge
*/
fun maxAge(name: String, maxAge: Int) {
actions.andExpect(matchers.maxAge(name, maxAge))
}
/**
* @see CookieResultMatchers.path
*/
fun path(name: String, matcher: Matcher<String>) {
actions.andExpect(matchers.path(name, matcher))
}
/**
* @see CookieResultMatchers.path
*/
fun path(name: String, path: String) {
actions.andExpect(matchers.path(name, path))
}
/**
* @see CookieResultMatchers.domain
*/
fun domain(name: String, matcher: Matcher<String>) {
actions.andExpect(matchers.domain(name, matcher))
}
/**
* @see CookieResultMatchers.domain
*/
fun domain(name: String, domain: String) {
actions.andExpect(matchers.domain(name, domain))
}
/**
* @see CookieResultMatchers.comment
*/
fun comment(name: String, matcher: Matcher<String>) {
actions.andExpect(matchers.comment(name, matcher))
}
/**
* @see CookieResultMatchers.comment
*/
fun comment(name: String, comment: String) {
actions.andExpect(matchers.comment(name, comment))
}
/**
* @see CookieResultMatchers.version
*/
fun version(name: String, matcher: Matcher<Int>) {
actions.andExpect(matchers.version(name, matcher))
}
/**
* @see CookieResultMatchers.version
*/
fun version(name: String, version: Int) {
actions.andExpect(matchers.version(name, version))
}
/**
* @see CookieResultMatchers.secure
*/
fun secure(name: String, secure: Boolean) {
actions.andExpect(matchers.secure(name, secure))
}
/**
* @see CookieResultMatchers.httpOnly
*/
fun httpOnly(name: String, httpOnly: Boolean) {
actions.andExpect(matchers.httpOnly(name, httpOnly))
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [FlashAttributeResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class FlashAttributeResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.flash()
/**
* @see FlashAttributeResultMatchers.attribute
*/
fun <T> attribute(name: String, matcher: Matcher<T>) {
actions.andExpect(matchers.attribute(name, matcher))
}
/**
* @see FlashAttributeResultMatchers.attribute
*/
fun attribute(name: String, value: Any?) {
actions.andExpect(matchers.attribute(name, value))
}
/**
* @see FlashAttributeResultMatchers.attributeExists
*/
fun attributeExists(vararg name: String) {
actions.andExpect(matchers.attributeExists(*name))
}
/**
* @see FlashAttributeResultMatchers.attributeCount
*/
fun attributeCount(count: Int) {
actions.andExpect(matchers.attributeCount(count))
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [HeaderResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class HeaderResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.header()
/**
* @see HeaderResultMatchersDsl.string
*/
fun string(name: String, matcher: Matcher<String>) {
actions.andExpect(matchers.string(name, matcher))
}
/**
* @see HeaderResultMatchersDsl.stringValues
*/
fun stringValues(name: String, matcher: Matcher<Iterable<String>>) {
actions.andExpect(matchers.stringValues(name, matcher))
}
/**
* @see HeaderResultMatchersDsl.string
*/
fun string(name: String, value: String) {
actions.andExpect(matchers.string(name, value))
}
/**
* @see HeaderResultMatchersDsl.stringValues
*/
fun stringValues(name: String, vararg value: String) {
actions.andExpect(matchers.stringValues(name, *value))
}
/**
* @see HeaderResultMatchersDsl.exists
*/
fun exists(name: String) {
actions.andExpect(matchers.exists(name))
}
/**
* @see HeaderResultMatchersDsl.doesNotExist
*/
fun doesNotExist(name: String) {
actions.andExpect(matchers.doesNotExist(name))
}
/**
* @see HeaderResultMatchersDsl.longValue
*/
fun longValue(name: String, value: Long) {
actions.andExpect(matchers.longValue(name, value))
}
/**
* @see HeaderResultMatchersDsl.dateValue
*/
fun dateValue(name: String, value: Long) {
actions.andExpect(matchers.dateValue(name, value))
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [JsonPathResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
@Suppress("UsePropertyAccessSyntax")
class JsonPathResultMatchersDsl internal constructor(@PublishedApi internal val actions: ResultActions, expression: String, vararg args: Any?) {
@PublishedApi
internal val matchers = MockMvcResultMatchers.jsonPath(expression, args)
/**
* @see JsonPathResultMatchers.prefix
*/
fun prefix(prefix: String) {
matchers.prefix(prefix)
}
/**
* @see JsonPathResultMatchers.value
*/
inline fun <reified T> value(matcher: Matcher<T>) {
actions.andExpect(matchers.value(matcher, T::class.java))
}
/**
* @see JsonPathResultMatchers.value
*/
fun value(expectedValue: Any?) {
actions.andExpect(matchers.value(expectedValue))
}
/**
* @see JsonPathResultMatchers.exists
*/
fun exists() {
actions.andExpect(matchers.exists())
}
/**
* @see JsonPathResultMatchers.doesNotExist
*/
fun doesNotExist() {
actions.andExpect(matchers.doesNotExist())
}
/**
* @see JsonPathResultMatchers.isEmpty
*/
fun isEmpty() {
actions.andExpect(matchers.isEmpty())
}
/**
* @see JsonPathResultMatchers.isNotEmpty
*/
fun isNotEmpty() {
actions.andExpect(matchers.isNotEmpty())
}
/**
* @see JsonPathResultMatchers.hasJsonPath
*/
fun hasJsonPath() {
actions.andExpect(matchers.hasJsonPath())
}
/**
* @see JsonPathResultMatchers.doesNotHaveJsonPath
*/
fun doesNotHaveJsonPath() {
actions.andExpect(matchers.doesNotHaveJsonPath())
}
/**
* @see JsonPathResultMatchers.isString
*/
fun isString() {
actions.andExpect(matchers.isString())
}
/**
* @see JsonPathResultMatchers.isBoolean
*/
fun isBoolean() {
actions.andExpect(matchers.isBoolean())
}
/**
* @see JsonPathResultMatchers.isNumber
*/
fun isNumber() {
actions.andExpect(matchers.isNumber())
}
/**
* @see JsonPathResultMatchers.isArray
*/
fun isArray() {
actions.andExpect(matchers.isArray())
}
/**
* @see JsonPathResultMatchers.isMap
*/
fun isMap() {
actions.andExpect(matchers.isMap())
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [ModelResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class ModelResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.model()
/**
* @see ModelResultMatchers.attribute
*/
fun <T> attribute(name: String, matcher: Matcher<T>) {
actions.andExpect(matchers.attribute(name, matcher))
}
/**
* @see ModelResultMatchers.attribute
*/
fun attribute(name: String, value: Any?) {
actions.andExpect(matchers.attribute(name, value))
}
/**
* @see ModelResultMatchers.attributeExists
*/
fun attributeExists(vararg name: String) {
actions.andExpect(matchers.attributeExists(*name))
}
/**
* @see ModelResultMatchers.attributeDoesNotExist
*/
fun attributeDoesNotExist(vararg name: String) {
actions.andExpect(matchers.attributeDoesNotExist(*name))
}
/**
* @see ModelResultMatchers.attributeErrorCount
*/
fun <T> attributeErrorCount(name: String, expectedCount: Int) {
actions.andExpect(matchers.attributeErrorCount(name, expectedCount))
}
/**
* @see ModelResultMatchers.attributeHasErrors
*/
fun attributeHasErrors(vararg name: String) {
actions.andExpect(matchers.attributeHasErrors(*name))
}
/**
* @see ModelResultMatchers.attributeHasNoErrors
*/
fun attributeHasNoErrors(vararg name: String) {
actions.andExpect(matchers.attributeHasNoErrors(*name))
}
/**
* @see ModelResultMatchers.attributeHasFieldErrors
*/
fun attributeHasFieldErrors(name: String, vararg fieldNames: String) {
actions.andExpect(matchers.attributeHasFieldErrors(name, *fieldNames))
}
/**
* @see ModelResultMatchers.attributeHasFieldErrorCode
*/
fun attributeHasFieldErrorCode(name: String, fieldName: String, code: String) {
actions.andExpect(matchers.attributeHasFieldErrorCode(name, fieldName, code))
}
/**
* @see ModelResultMatchers.attributeHasFieldErrorCode
*/
fun attributeHasFieldErrorCode(name: String, fieldName: String, matcher: Matcher<String>) {
actions.andExpect(matchers.attributeHasFieldErrorCode(name, fieldName, matcher))
}
/**
* @see ModelResultMatchers.errorCount
*/
fun errorCount(expectedCount: Int) {
actions.andExpect(matchers.errorCount(expectedCount))
}
/**
* @see ModelResultMatchers.hasErrors
*/
fun hasErrors() {
actions.andExpect(matchers.hasErrors())
}
/**
* @see ModelResultMatchers.hasNoErrors
*/
fun hasNoErrors() {
actions.andExpect(matchers.hasNoErrors())
}
/**
* @see ModelResultMatchers.size
*/
fun size(size: Int) {
actions.andExpect(matchers.size(size))
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [RequestResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class RequestResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.request()
/**
* @see RequestResultMatchers.asyncStarted
*/
fun asyncStarted() {
actions.andExpect(matchers.asyncStarted())
}
/**
* @see RequestResultMatchers.asyncStarted
*/
fun asyncNotStarted() {
actions.andExpect(matchers.asyncNotStarted())
}
/**
* @see RequestResultMatchers.asyncResult
*/
fun <T> asyncResult(matcher: Matcher<T>) {
actions.andExpect(matchers.asyncResult(matcher))
}
/**
* @see RequestResultMatchers.asyncResult
*/
fun asyncResult(expectedResult: Any?) {
actions.andExpect(matchers.asyncResult(expectedResult))
}
/**
* @see RequestResultMatchers.attribute
*/
fun <T> attribute(name: String, matcher: Matcher<T>) {
actions.andExpect(matchers.attribute(name, matcher))
}
/**
* @see RequestResultMatchers.attribute
*/
fun attribute(name: String, expectedValue: Any?) {
actions.andExpect(matchers.attribute(name, expectedValue))
}
/**
* @see RequestResultMatchers.sessionAttribute
*/
fun <T> sessionAttribute(name: String, matcher: Matcher<T>) {
actions.andExpect(matchers.sessionAttribute(name, matcher))
}
/**
* @see RequestResultMatchers.sessionAttribute
*/
fun sessionAttribute(name: String, expectedValue: Any?) {
actions.andExpect(matchers.sessionAttribute(name, expectedValue))
}
/**
* @see RequestResultMatchers.sessionAttributeDoesNotExist
*/
fun sessionAttributeDoesNotExist(vararg names: String) {
actions.andExpect(matchers.sessionAttributeDoesNotExist(*names))
}
}

View File

@ -0,0 +1,515 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [StatusResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
@Suppress("UsePropertyAccessSyntax")
class StatusResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.status()
/**
* @see StatusResultMatchers.is
*/
fun isEqualTo(matcher: Matcher<Int>) {
actions.andExpect(matchers.`is`(matcher))
}
/**
* @see StatusResultMatchers.is
*/
fun isEqualTo(status: Int) {
actions.andExpect(matchers.`is`(status))
}
/**
* @see StatusResultMatchers.is1xxInformational
*/
fun is1xxInformational() {
actions.andExpect(matchers.is1xxInformational())
}
/**
* @see StatusResultMatchers.is2xxSuccessful
*/
fun is2xxSuccessful() {
actions.andExpect(matchers.is2xxSuccessful())
}
/**
* @see StatusResultMatchers.is3xxRedirection
*/
fun is3xxRedirection() {
actions.andExpect(matchers.is3xxRedirection())
}
/**
* @see StatusResultMatchers.is4xxClientError
*/
fun is4xxClientError() {
actions.andExpect(matchers.is4xxClientError())
}
/**
* @see StatusResultMatchers.is5xxServerError
*/
fun is5xxServerError() {
actions.andExpect(matchers.is5xxServerError())
}
/**
* @see StatusResultMatchers.reason
*/
fun reason(matcher: Matcher<String>) {
actions.andExpect(matchers.reason(matcher))
}
/**
* @see StatusResultMatchers.reason
*/
fun reason(reason: String) {
actions.andExpect(matchers.reason(reason))
}
/**
* @see StatusResultMatchers.isContinue
*/
fun isContinue() {
actions.andExpect(matchers.isContinue())
}
/**
* @see StatusResultMatchers.isSwitchingProtocols
*/
fun isSwitchingProtocols() {
actions.andExpect(matchers.isSwitchingProtocols())
}
/**
* @see StatusResultMatchers.isProcessing
*/
fun isProcessing() {
actions.andExpect(matchers.isProcessing())
}
/**
* @see StatusResultMatchers.isCheckpoint
*/
fun isCheckpoint() {
actions.andExpect(matchers.isCheckpoint())
}
/**
* @see StatusResultMatchers.isOk
*/
fun isOk() {
actions.andExpect(matchers.isOk())
}
/**
* @see StatusResultMatchers.isCreated
*/
fun isCreated() {
actions.andExpect(matchers.isCreated())
}
/**
* @see StatusResultMatchers.isAccepted
*/
fun isAccepted() {
actions.andExpect(matchers.isAccepted())
}
/**
* @see StatusResultMatchers.isNonAuthoritativeInformation
*/
fun isNonAuthoritativeInformation() {
actions.andExpect(matchers.isNonAuthoritativeInformation())
}
/**
* @see StatusResultMatchers.isNoContent
*/
fun isNoContent() {
actions.andExpect(matchers.isNoContent())
}
/**
* @see StatusResultMatchers.isResetContent
*/
fun isResetContent() {
actions.andExpect(matchers.isResetContent())
}
/**
* @see StatusResultMatchers.isPartialContent
*/
fun isPartialContent() {
actions.andExpect(matchers.isPartialContent())
}
/**
* @see StatusResultMatchers.isMultiStatus
*/
fun isMultiStatus() {
actions.andExpect(matchers.isMultiStatus())
}
/**
* @see StatusResultMatchers.isAlreadyReported
*/
fun isAlreadyReported() {
actions.andExpect(matchers.isAlreadyReported())
}
/**
* @see StatusResultMatchers.isImUsed
*/
fun isImUsed() {
actions.andExpect(matchers.isImUsed())
}
/**
* @see StatusResultMatchers.isMultipleChoices
*/
fun isMultipleChoices() {
actions.andExpect(matchers.isMultipleChoices())
}
/**
* @see StatusResultMatchers.isFound
*/
fun isFound() {
actions.andExpect(matchers.isFound())
}
/**
* @see StatusResultMatchers.isSeeOther
*/
fun isSeeOther() {
actions.andExpect(matchers.isSeeOther())
}
/**
* @see StatusResultMatchers.isNotModified
*/
fun isNotModified() {
actions.andExpect(matchers.isNotModified())
}
/**
* @see StatusResultMatchers.isTemporaryRedirect
*/
fun isTemporaryRedirect() {
actions.andExpect(matchers.isTemporaryRedirect())
}
/**
* @see StatusResultMatchers.isPermanentRedirect
*/
fun isPermanentRedirect() {
actions.andExpect(matchers.isPermanentRedirect())
}
/**
* @see StatusResultMatchers.isBadRequest
*/
fun isBadRequest() {
actions.andExpect(matchers.isBadRequest())
}
/**
* @see StatusResultMatchers.isUnauthorized
*/
fun isUnauthorized() {
actions.andExpect(matchers.isUnauthorized())
}
/**
* @see StatusResultMatchers.isPaymentRequired
*/
fun isPaymentRequired() {
actions.andExpect(matchers.isPaymentRequired())
}
/**
* @see StatusResultMatchers.isForbidden
*/
fun isForbidden() {
actions.andExpect(matchers.isForbidden())
}
/**
* @see StatusResultMatchers.isNotFound
*/
fun isNotFound() {
actions.andExpect(matchers.isNotFound())
}
/**
* @see StatusResultMatchers.isMethodNotAllowed
*/
fun isMethodNotAllowed() {
actions.andExpect(matchers.isMethodNotAllowed())
}
/**
* @see StatusResultMatchers.isNotAcceptable
*/
fun isNotAcceptable() {
actions.andExpect(matchers.isNotAcceptable())
}
/**
* @see StatusResultMatchers.isProxyAuthenticationRequired
*/
fun isProxyAuthenticationRequired() {
actions.andExpect(matchers.isProxyAuthenticationRequired())
}
/**
* @see StatusResultMatchers.isRequestTimeout
*/
fun isRequestTimeout() {
actions.andExpect(matchers.isRequestTimeout())
}
/**
* @see StatusResultMatchers.isConflict
*/
fun isConflict() {
actions.andExpect(matchers.isConflict())
}
/**
* @see StatusResultMatchers.isGone
*/
fun isGone() {
actions.andExpect(matchers.isGone())
}
/**
* @see StatusResultMatchers.isLengthRequired
*/
fun isLengthRequired() {
actions.andExpect(matchers.isLengthRequired())
}
/**
* @see StatusResultMatchers.isPreconditionFailed
*/
fun isPreconditionFailed() {
actions.andExpect(matchers.isPreconditionFailed())
}
/**
* @see StatusResultMatchers.isPayloadTooLarge
*/
fun isPayloadTooLarge() {
actions.andExpect(matchers.isPayloadTooLarge())
}
/**
* @see StatusResultMatchers.isUriTooLong
*/
fun isUriTooLong() {
actions.andExpect(matchers.isUriTooLong())
}
/**
* @see StatusResultMatchers.isUnsupportedMediaType
*/
fun isUnsupportedMediaType() {
actions.andExpect(matchers.isUnsupportedMediaType())
}
/**
* @see StatusResultMatchers.isRequestedRangeNotSatisfiable
*/
fun isRequestedRangeNotSatisfiable() {
actions.andExpect(matchers.isRequestedRangeNotSatisfiable())
}
/**
* @see StatusResultMatchers.isExpectationFailed
*/
fun isExpectationFailed() {
actions.andExpect(matchers.isExpectationFailed())
}
/**
* @see StatusResultMatchers.isIAmATeapot
*/
fun isIAmATeapot() {
actions.andExpect(matchers.isIAmATeapot())
}
/**
* @see StatusResultMatchers.isUnprocessableEntity
*/
fun isUnprocessableEntity() {
actions.andExpect(matchers.isUnprocessableEntity())
}
/**
* @see StatusResultMatchers.isLocked
*/
fun isLocked() {
actions.andExpect(matchers.isLocked())
}
/**
* @see StatusResultMatchers.isFailedDependency
*/
fun isFailedDependency() {
actions.andExpect(matchers.isFailedDependency())
}
/**
* @see StatusResultMatchers.isTooEarly
*/
fun isTooEarly() {
actions.andExpect(matchers.isTooEarly())
}
/**
* @see StatusResultMatchers.isUpgradeRequired
*/
fun isUpgradeRequired() {
actions.andExpect(matchers.isUpgradeRequired())
}
/**
* @see StatusResultMatchers.isPreconditionRequired
*/
fun isPreconditionRequired() {
actions.andExpect(matchers.isPreconditionRequired())
}
/**
* @see StatusResultMatchers.isTooManyRequests
*/
fun isTooManyRequests() {
actions.andExpect(matchers.isTooManyRequests())
}
/**
* @see StatusResultMatchers.isRequestHeaderFieldsTooLarge
*/
fun isRequestHeaderFieldsTooLarge() {
actions.andExpect(matchers.isRequestHeaderFieldsTooLarge())
}
/**
* @see StatusResultMatchers.isUnavailableForLegalReasons
*/
fun isUnavailableForLegalReasons() {
actions.andExpect(matchers.isUnavailableForLegalReasons())
}
/**
* @see StatusResultMatchers.isInternalServerError
*/
fun isInternalServerError() {
actions.andExpect(matchers.isInternalServerError())
}
/**
* @see StatusResultMatchers.isNotImplemented
*/
fun isNotImplemented() {
actions.andExpect(matchers.isNotImplemented())
}
/**
* @see StatusResultMatchers.isBadGateway
*/
fun isBadGateway() {
actions.andExpect(matchers.isBadGateway())
}
/**
* @see StatusResultMatchers.isServiceUnavailable
*/
fun isServiceUnavailable() {
actions.andExpect(matchers.isServiceUnavailable())
}
/**
* @see StatusResultMatchers.isGatewayTimeout
*/
fun isGatewayTimeout() {
actions.andExpect(matchers.isGatewayTimeout())
}
/**
* @see StatusResultMatchers.isHttpVersionNotSupported
*/
fun isHttpVersionNotSupported() {
actions.andExpect(matchers.isHttpVersionNotSupported())
}
/**
* @see StatusResultMatchers.isVariantAlsoNegotiates
*/
fun isVariantAlsoNegotiates() {
actions.andExpect(matchers.isVariantAlsoNegotiates())
}
/**
* @see StatusResultMatchers.isInsufficientStorage
*/
fun isInsufficientStorage() {
actions.andExpect(matchers.isInsufficientStorage())
}
/**
* @see StatusResultMatchers.isLoopDetected
*/
fun isLoopDetected() {
actions.andExpect(matchers.isLoopDetected())
}
/**
* @see StatusResultMatchers.isBandwidthLimitExceeded
*/
fun isBandwidthLimitExceeded() {
actions.andExpect(matchers.isBandwidthLimitExceeded())
}
/**
* @see StatusResultMatchers.isNotExtended
*/
fun isNotExtended() {
actions.andExpect(matchers.isNotExtended())
}
/**
* @see StatusResultMatchers.isNetworkAuthenticationRequired
*/
fun isNetworkAuthenticationRequired() {
actions.andExpect(matchers.isNetworkAuthenticationRequired())
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
/**
* Provide a [ViewResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class ViewResultMatchersDsl internal constructor (private val actions: ResultActions) {
private val matchers = MockMvcResultMatchers.view()
/**
* @see ViewResultMatchers.name
*/
fun name(matcher: Matcher<String>) {
actions.andExpect(matchers.name(matcher))
}
/**
* @see ViewResultMatchers.name
*/
fun name(expectedViewName: String) {
actions.andExpect(matchers.name(expectedViewName))
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.result
import org.hamcrest.Matcher
import org.springframework.test.web.servlet.ResultActions
import org.w3c.dom.Node
import org.w3c.dom.NodeList
/**
* Provide a [XpathResultMatchers] Kotlin DSL in order to be able to write idiomatic Kotlin code.
*
* @author Sebastien Deleuze
* @since 5.3
*/
class XpathResultMatchersDsl internal constructor (private val actions: ResultActions, expression: String, namespaces: Map<String, String>? = null, vararg args: Any?) {
private val matchers = if (namespaces == null) MockMvcResultMatchers.xpath(expression, args) else MockMvcResultMatchers.xpath(expression, namespaces, args)
/**
* @see XpathResultMatchers.node
*/
fun node(matcher: Matcher<Node>) {
actions.andExpect(matchers.node(matcher))
}
/**
* @see XpathResultMatchers.nodeList
*/
fun nodeList(matcher: Matcher<NodeList>) {
actions.andExpect(matchers.nodeList(matcher))
}
/**
* @see XpathResultMatchers.exists
*/
fun exists() {
actions.andExpect(matchers.exists())
}
/**
* @see XpathResultMatchers.doesNotExist
*/
fun doesNotExist() {
actions.andExpect(matchers.doesNotExist())
}
/**
* @see XpathResultMatchers.nodeCount
*/
fun nodeCount(matcher: Matcher<Int>) {
actions.andExpect(matchers.nodeCount(matcher))
}
/**
* @see XpathResultMatchers.nodeCount
*/
fun nodeCount(expectedCount: Int) {
actions.andExpect(matchers.nodeCount(expectedCount))
}
/**
* @see XpathResultMatchers.string
*/
fun string(matcher: Matcher<String>) {
actions.andExpect(matchers.string(matcher))
}
/**
* @see XpathResultMatchers.string
*/
fun string(expectedValue: String) {
actions.andExpect(matchers.string(expectedValue))
}
/**
* @see XpathResultMatchers.number
*/
fun number(matcher: Matcher<Double>) {
actions.andExpect(matchers.number(matcher))
}
/**
* @see XpathResultMatchers.number
*/
fun number(expectedValue: Double) {
actions.andExpect(matchers.number(expectedValue))
}
/**
* @see XpathResultMatchers.booleanValue
*/
fun booleanValue(value: Boolean) {
actions.andExpect(matchers.booleanValue(value))
}
}

View File

@ -18,6 +18,8 @@ package org.springframework.test.web.servlet
import org.assertj.core.api.Assertions.*
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.junit.jupiter.api.Test
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
@ -25,6 +27,7 @@ import org.springframework.http.MediaType.*
import org.springframework.test.web.Person
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.bind.annotation.*
import org.springframework.web.servlet.ModelAndView
import reactor.core.publisher.Mono
import java.security.Principal
import java.util.*
@ -50,7 +53,7 @@ class MockMvcExtensionsTests {
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
status { isOk() }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
@ -62,7 +65,7 @@ class MockMvcExtensionsTests {
@Test
fun `request without MockHttpServletRequestDsl`() {
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee").andExpect {
status { isOk }
status { isOk() }
}.andDo {
print()
}
@ -75,7 +78,7 @@ class MockMvcExtensionsTests {
val matcher = ResultMatcher { matcherInvoked = true }
val handler = ResultHandler { handlerInvoked = true }
mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee").andExpect {
status { isOk }
status { isOk() }
}.andExpect {
match(matcher)
}.andDo {
@ -98,7 +101,7 @@ class MockMvcExtensionsTests {
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
status { isOk() }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
@ -117,7 +120,7 @@ class MockMvcExtensionsTests {
}
}.andExpect {
status {
isCreated
isCreated()
}
}
}
@ -139,7 +142,7 @@ class MockMvcExtensionsTests {
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { model { attributeExists("name", "wrong") } }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { redirectedUrl("wrong/Url") }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { redirectedUrlPattern("wrong/Url") }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { status { isAccepted } }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { status { isAccepted() } }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { view { name("wrongName") } }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { jsonPath("name") { value("wrong") } }
}
@ -150,7 +153,7 @@ class MockMvcExtensionsTests {
mockMvc.get("/person/Clint") {
accept = APPLICATION_XML
}.andExpect {
status { isOk }
status { isOk() }
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { xpath("//wrong") { nodeCount(1) } }
}.andDo {
print()
@ -160,7 +163,17 @@ class MockMvcExtensionsTests {
@Test
fun asyncDispatch() {
mockMvc.get("/async").asyncDispatch().andExpect {
status { isOk }
status { isOk() }
}
}
@Test
fun modelAndView() {
mockMvc.get("/").andExpect {
model {
assertThatExceptionOfType(AssertionError::class.java).isThrownBy { attribute("foo", "bar") }
attribute("foo", "foo")
}
}
}
@ -182,5 +195,8 @@ class MockMvcExtensionsTests {
fun getAsync(): Mono<Person> {
return Mono.just(Person("foo"))
}
@GetMapping("/")
fun index() = ModelAndView("index", mapOf("foo" to "foo", "bar" to "bar"))
}
}