1

I am trying to create a test of a Spring Boot controller using TestRestTemplate. It is a requirement that only what is absolutely required for the controller be included in the test context, so spinning up the entire application context for the test is not an option.

Currently the test fails due to the endpoint returning 404. The endpoint works correctly in production. It appears that the controller is not being registered with the web servlet.

The controller appears as follows:

@RestController
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): ResponseDto {
        return ResponseDto(data = "Some data")
    }
}

data class ResponseDto(val data: String)

The test is as follows:

@SpringBootTest(
    classes = [MyController::class, ServletWebServerFactoryAutoConfiguration::class],
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
internal class MyControllerTestRestTemplateTest(
    @Autowired private val restTemplate: TestRestTemplate
) {
    @Test
    fun `should work`() {
        val result = restTemplate.getForEntity("/endpoint", String::class.java)

        result.body.shouldMatchJson(
            """
                {
                    "data": "Some data"
                }
            """)
    }
}

How can I get this test setup to work?

user2615350
  • 263
  • 2
  • 13
  • can you try to remove the `ServletWebServerFactoryAutoConfiguration` from your annotation in the test. – rieckpil Mar 03 '20 at 18:38

3 Answers3

0

It is a requirement that only what is absolutely required for the controller be included in the test context,...

SpringBoot has a tooling for that already - see @WebMvcTest slice documentation or this SO answer.

Josef Cech
  • 2,115
  • 16
  • 17
  • Thanks. I had tried with `@WebMvcTest` but ran into different problems. Nevertheless I was able to solve those eventually so have a complete solution now. – user2615350 Mar 06 '20 at 08:48
0

As your requirement states:

It is a requirement that only what is absolutely required for the controller be included in the test context, so spinning up the entire application context for the test is not an option.

You should consider testing just the web layer using @WebMvcTest. With your current @SpringBootTest and the random port, you are booting the whole Spring Context and start also the embedded Tomcat. With @WebMvcTest you can inject a MockMvc instance and write assertions on the response body/header/status.

A Java example may look like this

@WebMvcTest(MyController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void testExample() throws Exception {
        this.mvc.perform(get("/endpoint")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("YOUR_STRING_HERE"));
    }
}

And a working Kotlin example looks like the following

@WebMvcTest(MyController::class)
internal class MyControllerTests(@Autowired private val mockMvc: MockMvc) {

  @Test
  fun testExample() {
    this.mockMvc.perform(MockMvcRequestBuilders.get("/endpoint")
      .accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk)
      .andExpect(content().json("""
        {
         "data": "Some data"
        }
      """.trimIndent()))
  }
}
rieckpil
  • 10,470
  • 3
  • 32
  • 56
0

The answers by rieckpil and Josef are both true and I agree that using @WebMvcTest is a better approach.

If you insist on keep using @SpringBootTest and TestRestTemplate: You are using webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT. This means your TestRestTemplate doesn't know what port to use. You need to include the entire url including the port the application is running on in the string url

By adding

@LocalServerPort
int randomServerPort = 0

And then providing the complete url

val result = restTemplate.getForEntity("http://localhost:${randomServerPort}/endpoint", String::class.java)
I.Brok
  • 319
  • 1
  • 2
  • 16
  • Thanks for the suggestion. I've tried it but unfortunately I still get a 404. It seems that it was using the correct port but somehow didn't register the controller with the container. – user2615350 Mar 06 '20 at 08:47