Reputation: 26878
I have written a small test on Spring REST docs with custom Jackson module (using Spring Boot 1.3). In my application main class, I only have @SpringBootApplication
. I then have another class JacksonCustomizations
that looks like this:
@Configuration
public class JacksonCustomizations {
@Bean
public Module myCustomModule() {
return new MyCustomModule();
}
static class MyCustomModule extends SimpleModule {
public MyCustomModule() {
addSerializer(ImmutableEntityId.class, new JsonSerializer<ImmutableEntityId>() {
@Override
public void serialize(ImmutableEntityId immutableEntityId, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeNumber( (Long)immutableEntityId.getId() );
}
});
}
}
}
This customization is picked up perfectly. When I run the Spring Boot application, I see the JSON as it should be.
However, in my documentation test, the customization is not applied. This is the code of my test:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
@WebAppConfiguration
public class NoteControllerDocumentation {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
public void notesListExample() throws Exception {
mockMvc.perform(get("/api/notes/"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andDo(document("notes-list-example", responseFields(
fieldWithPath("[]").description("An array of <<note-example,note>>s."))));
}
@Configuration
@EnableWebMvc
@Import(JacksonCustomizations.class)
public static class TestConfiguration {
@Bean
public NoteController noteController() {
return new NoteController();
}
}
}
Note how the application context in my test imports the JacksonCustomizations
configuration.
Other things I found:
@EnableWebMvc
on my boot application stops the customization from working. @EnableWebMvc
on my test stops JSON being produced.Upvotes: 4
Views: 2404
Reputation: 116251
NoteControllerDocumentation
isn't configured to use Spring Boot to create the application context. This means that Spring Boot's auto-configuration doesn't run and, therefore, your custom Jackson module isn't applied to the ObjectMapper
.
The simplest solution to your problem is to delete your TestConfiguration
class and update SpringApplicationConfiguration
to reference DemoApplication
instead. That'll leave you with the following code:
package com.example.controller;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.example.DemoApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
@WebAppConfiguration
public class NoteControllerDocumentation {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
public void notesListExample() throws Exception {
mockMvc.perform(get("/api/notes/"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(content().json("[{\"id\":1}]"))
.andDo(print())
.andDo(document("nodes-list-example", responseFields(
fieldWithPath("[]").description("An array of <<note-example,note>>s."))));
}
}
Alternatively, if you want more control over how your control is created (to inject a mock service, for example), you can use a custom configuration class. The key is to annotate that class with @EnableAutoConfiguration
so that Spring Boot's auto-configuration is enabled and the customization of the ObjectMapper
is performed. This approach will leave you with the following code:
package com.example.controller;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentation;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.example.JacksonCustomizations;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
@WebAppConfiguration
public class NoteControllerDocumentation {
@Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(documentationConfiguration(restDocumentation))
.build();
}
@Test
public void notesListExample() throws Exception {
mockMvc.perform(get("/api/notes/"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(content().json("[{\"id\":1}]"))
.andDo(print())
.andDo(document("nodes-list-example", responseFields(
fieldWithPath("[]").description("An array of <<note-example,note>>s."))));
}
@Configuration
@EnableAutoConfiguration
@Import(JacksonCustomizations.class)
static class TestConfiguration {
@Bean
public NoteController notesController() {
return new NoteController();
}
}
}
Upvotes: 2