1. Overview
In this quick tutorial, we’ll have a look at the new Kotlin-specific MockMvc support available in Spring Framework 5.2+.
Note: as version 5.2 is not GA yet, we need to use the Spring Milestone repository.
2. The Controller to Test
Let’s set up the controller that we’ll be testing**.**
We’ll be using the following domain:
// Payload
data class Name(val first: String, val last: String)
// Web request
data class Request(val name: Name)
// Web response
@JsonInclude(JsonInclude.Include.NON_NULL) data class Response(val error: String?)
And a REST controller that validates the payload it receives:
@RestController
@RequestMapping("/mockmvc")
class MockMvcController {
@RequestMapping(value = ["/validate"], method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE])
fun validate(@RequestBody request: Request): Response {
val error = if (request.name.first == "admin") {
null
} else {
ERROR
}
return Response(error)
}
companion object {
const val ERROR = "invalid user"
}
}
Here, we have a POST endpoint that receives an instance of our custom Request class serialized to JSON and returns an instance of our custom Response class serialized to JSON.
3. Classic Testing Approach
We can test the controller above using the standard MockMvc approach:
mockMvc.perform(MockMvcRequestBuilders
.post("/mockmvc/validate")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(Request(Name("admin", "")))))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.content().string("{}"))
In the example above, we’ve used the standard testing support to verify a few aspects:
- the HTTP response code is 200
- the response’s Content-Type is application/json
- the response content is an empty JSON object
Actually, it looks quite good — clean and expressive. But we can make it even cleaner with Kotlin DSL.
4. Modern Testing Approach
The same test can be rewritten as:
mockMvc.post("/mockmvc/validate") {
contentType = MediaType.APPLICATION_JSON
content = mapper.writeValueAsString(Request(Name("admin", "")))
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
content { json("{}") }
}
Let’s check how this is implemented. First of all, we need to use the Spring Framework 5.2+, which contains MockMvcExtensions.kt — a custom DSL for MockMvc.
We start by calling the extension function MockMvc.post() with a MockHttpServletRequestDsl extension method:
mockMvc.post("/mockmvc/validate") {
// This is extension method's body
}
This means the method is executed with a MockHttpServletRequestDsl object as this reference, so we’ve just set its contentType, content, and accept properties.
Then we define the expectations in a similar way — by providing a MockMvcResultMatchersDsl extension method:
andExpect {
// Extension method body
}
5. Further Evolution
If we want to add more tests, we can refactor the existing code to avoid duplication.
Let’s extract the common code here into a helpful doTest method:
private fun doTest(input: Request, expectation: Response) {
mockMvc.post("/mockmvc/validate") {
contentType = MediaType.APPLICATION_JSON
content = mapper.writeValueAsString(input)
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
content { json(mapper.writeValueAsString(expectation)) }
}
}
Now, the actual tests will be simplified:
@Test
fun `when supported user is given then validation is successful`() {
doTest(Request(Name("admin", "")), Response(null))
}
@Test
fun `when unsupported user is given then validation is failed`() {
doTest(Request(Name("some-name", "some-surname")), Response(MockMvcController.ERROR))
}
6. Conclusion
In this article, we checked how MockMvc Kotlin DSL can be used to make our test code cleaner. It’s not a silver bullet, just a little feature that makes the test code even more concise.
As this is one more example of custom DSL usage, it can serve as a motivation to start using custom DSL in other projects as well.
As usual, the complete source code for this article is available over on GitHub.