4

I'm trying to create a MockMvc test of a Spring Boot controller. I specifically do not want the entire application context to be spun up, so I am restricting the context to the controller in question. However, the test fails with a 500 with the following log output:

2020-03-03 13:04:06.904  WARN 8207 --- [           main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class main.endpoints.ResponseDto]

It appears that the Spring Boot context does not know how to find Jackson.

Here is the controller

@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],
    webEnvironment = SpringBootTest.WebEnvironment.MOCK
)
@AutoConfigureMockMvc
internal class MyControllerTest(@Autowired private val mockMvc: MockMvc) {
    @Test
    fun `should work`() {
        mockMvc.perform(MockMvcRequestBuilders.get("/endpoint").accept(MediaType.APPLICATION_JSON))
            .andExpect(
                content().json(
                    """
                        {
                            "data": "Some data"
                        }
                    """
                )
            )
    }
}

The build.gradle file includes the following dependencies:

    def jacksonVersion = "2.10.2"
    testImplementation("com.fasterxml.jackson.core:jackson-core:2.10.2")
    testImplementation("com.fasterxml.jackson.core:jackson-databind:2.10.2")
    testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.10.2")

Any ideas on how to get this to work?

user2615350
  • 263
  • 2
  • 13

3 Answers3

6

The solution is to annotate the class with @WebMvcTest rather than @SpringBootTest. This configures enough context that the test can interact via MockMvc with the controller.

Unfortunately, enabling @WebMvcTest has another side effect: all beans specified by @Bean-annotated methods in the configuration are also instantiated. This is a problem when those methods cannot be executed in a test environment (e.g. because they access certain environment variables).

To solve this, I added the annotation @ActiveProfiles("test") to the test and @Profile("!test") to each such annotated method. This suppresses the invocation of those methods and the test works.

user2615350
  • 263
  • 2
  • 13
0

I'm not sure, but I think you need to specify what format the output will be. So something like

@GetMapping(value = ["/endpoint"], produces = [MediaType.APPLICATION_JSON])

So that spring knows to convert it to json and not say XML or something.

I.Brok
  • 319
  • 1
  • 2
  • 16
  • Adding `produces = "application/json"` changes the error to the following: `org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation` The endpoint works fine in production, so it doesn't make sense that it should have to be changed for tests. – user2615350 Mar 04 '20 at 09:33
  • Ah, I didn't realise it was a test specific situation. In that case the problem may be something with your test Context, that something is missing. I think you are trying to do something manually with preventing the complete context to spin up, that spring boot already supports. Change the annotation above the test to @WebMvcTest(MyController.class) and autowire MockMvc and it should do what you want (just spinning up the web layer, with only that one controller), also see: https://spring.io/guides/gs/testing-web/ (WebMvcTest is explained all the way at the bottom. – I.Brok Mar 04 '20 at 11:00
0

@EnableWebMvc might solve your issue. According to Java Doc:

Adding this annotation to an @Configuration class imports the Spring MVC configuration from WebMvcConfigurationSupport,

e.g.:
 @Configuration
 @EnableWebMvc
 @ComponentScan(basePackageClasses = MyConfiguration.class)
 public class MyConfiguration {

 }

To customize the imported configuration, implement the interface WebMvcConfigurer and override individual methods, e.g.:

 @Configuration
 @EnableWebMvc
 @ComponentScan(basePackageClasses = MyConfiguration.class)
 public class MyConfiguration implements WebMvcConfigurer {

           @Override
           public void addFormatters(FormatterRegistry formatterRegistry) {
         formatterRegistry.addConverter(new MyConverter());
           }

           @Override
           public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
         converters.add(new MyHttpMessageConverter());
           }

 }