Spring Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

If you have a few years of Linux experience, and you're interested in sharing that with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

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.

Spring bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!