Annotated controllers – Spring Web/Webflux and Testing
Spring Webflux and Spring Web are two entirely different web stacks. Spring Webflux, however, continues to support an annotation-based programming model
An endpoint defined using these two stacks may look similar but the way to test such an endpoint is fairly different and a user writing such an endpoint has to be aware of which stack is active and formulate the test accordingly.
Sample Endpoint
Consider a sample annotation based endpoint:
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController data class Greeting(val message: String) @RestController @RequestMapping("/web") class GreetingController { @PostMapping("/greet") fun handleGreeting(@RequestBody greeting: Greeting): Greeting { return Greeting("Thanks: ${greeting.message}") } }
Testing with Spring Web
If Spring Boot 2 starters were used to create this application with Spring Web as the starter, specified using a Gradle build file the following way:
compile('org.springframework.boot:spring-boot-starter-web')
then the test of such an endpoint would be using a Mock web runtime, referred to as Mock MVC:
import org.junit.Test import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.test.context.junit4.SpringRunner import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content @RunWith(SpringRunner::class) @WebMvcTest(GreetingController::class) class GreetingControllerMockMvcTest { @Autowired lateinit var mockMvc: MockMvc @Test fun testHandleGreetings() { mockMvc .perform( post("/web/greet") .content(""" |{ |"message": "Hello Web" |} """.trimMargin()) ).andExpect(content().json(""" |{ |"message": "Thanks: Hello Web" |} """.trimMargin())) } }
Testing with Spring Web-Flux
If on the other hand Spring-Webflux starters were pulled in, say with the following Gradle dependency:
compile('org.springframework.boot:spring-boot-starter-webflux')
then the test of this endpoint would be using the excellent WebTestClient class, along these lines:
import org.junit.Test import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org.springframework.http.HttpHeaders import org.springframework.test.context.junit4.SpringRunner import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.web.reactive.function.BodyInserters @RunWith(SpringRunner::class) @WebFluxTest(GreetingController::class) class GreetingControllerTest { @Autowired lateinit var webTestClient: WebTestClient @Test fun testHandleGreetings() { webTestClient.post() .uri("/web/greet") .header(HttpHeaders.CONTENT_TYPE, "application/json") .body(BodyInserters .fromObject(""" |{ | "message": "Hello Web" |} """.trimMargin())) .exchange() .expectStatus().isOk .expectBody() .json(""" |{ | "message": "Thanks: Hello Web" |} """.trimMargin()) } }
Conclusion
It is easy to assume that since the programming model looks very similar using Spring Web and Spring Webflux stacks, that the tests for such a legacy test using Spring Web would continue over to Spring Webflux, this is however not true, as a developer we have to be mindful of the underlying stack that comes into play and formulate the test accordingly. I hope this post clarifies how such a test should be crafted.
Published on Java Code Geeks with permission by Biju Kunjummen, partner at our JCG program. See the original article here: Annotated controllers – Spring Web/Webflux and Testing Opinions expressed by Java Code Geeks contributors are their own. |