3

I have set up spring boot application using Gradle. Now I do understand that @EnableAutoConnfiguration configures the application based on dependencies in a class path. I am pretty happy to avoid all of the plumbing but things start happening which I wish wouldn't.

Here are my dependencies:

dependencies {
        compile('org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE')
        compile 'org.springframework.hateoas:spring-hateoas:0.17.0.RELEASE'
        compile 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE'
        compile 'org.springframework.boot:spring-boot-starter-data-jpa'

        compile 'com.google.guava:guava:18.0'
        compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
        compile 'commons-beanutils:commons-beanutils:1.9.2'
        runtime 'org.hsqldb:hsqldb:2.3.2'

        testCompile 'org.springframework.boot:spring-boot-starter-test'
        testCompile 'com.jayway.jsonpath:json-path:2.0.0'
    }

My application class:

@ComponentScan("org.home.project")
@SpringBootApplication
//@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

A snippet from UserController:

@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<ResourceSupport> create(@Valid @RequestBody UserCreateRequest ucr, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) throw new InvalidRequestException("Bad Request", bindingResult);

    Long userId = userService.create(ucr);

    ResourceSupport resource = new ResourceSupport();
    resource.add(linkTo(UserEndpoint.class).withSelfRel());
    resource.add(linkTo(methodOn(UserEndpoint.class).update(userId, null, null)).withRel(VIEW_USER));
    resource.add(linkTo(methodOn(UserEndpoint.class).delete(userId)).withRel(DELETE_USER));

    return new ResponseEntity(resource, HttpStatus.CREATED);
}

The UserController.java has two annotations:

@RestController
@RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)

First of - notice the commented out @EnableHyperdiaSupport annotation - links in the ResourceSupport instance are still serialized to hal+json format despite media type produced and media type set in the request. This happens automatically when 'org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE' is introduced in the dependencies. How would one go about configuring it explicitly ?

Another issue are unit tests.

This passes:

@RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = MockServletContext.class)
    @WebAppConfiguration
    public class UserControllerTest {
    ...ommited for brevity...

@InjectMocks
    private UserController testObject;

    @Before
    public void setUp() throws Exception {
        initMocks(this);
        mockMvc = standaloneSetup(testObject).build();
    }

    @Test
        public void testUserCreatedLinks() throws Exception {
            mockMvc.perform(post("/users").contentType(MediaType.APPLICATION_JSON).content(data))
            .andExpect(status().isCreated())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.links.[*].rel", hasItem("self")));
        }
    ...ommited fro brevity...
    }

The post request returns a standard JSON response in the test - not HAL+JSON. Is there a way to reconfigure this so that unit testing @RestController with MockServletContext would produce HAL+JSON or getting back to problem number 1 - how to configure the response format explicitly so that Jackson serializer would not produce hal+json ?

Xeperis
  • 1,449
  • 2
  • 25
  • 41
  • 1
    have you seen `org.springframework.hateoas.MediaTypes.HAL_JSON` ? – Joao Evangelista Apr 20 '15 at 16:42
  • Hm, but @JoaoEvangelista then why a request at runtime to the controller with explicitly defined Media-Type: "application/json" returns a response with media type of "application/json" but is still "application/hal+json" flavored ? if I remove produces value from the mapping configuration then response has Media-Type: "application/hal-json". – Xeperis Apr 20 '15 at 16:55

1 Answers1

6

You're running your test using Spring MVC Test's standaloneSetup which uses a bare minimum of configuration to get your controller up and running. That configuration isn't the same as the configuration that will be used when you run the whole application.

If you want to use the same configuration, you could use webAppContextSetup:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class SomeTests {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }
}

Alternatively, you can replicate Spring HATEOAS's configuration in the standalone setup. Note that this runs the risk of your tests' configuration deviating from your application's configuration. You'd create the MockMvc instance like this:

TypeConstrainedMappingJackson2HttpMessageConverter messageConverter = 
            new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));

ObjectMapper objectMapper = messageConverter.getObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.setHandlerInstantiator(
        new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null));

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testObject)
        .setMessageConverters(messageConverter).build();
Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • But is there a way to add some configuration to bare minimum without switching contexts ? – Xeperis Apr 21 '15 at 14:58
  • 1
    It's possible, although I'm not sure I'd recommend it. See my edit. – Andy Wilkinson Apr 21 '15 at 17:15
  • Late to this, I know, but this might help someone else in the future: I didn't like the idea of replicating the HATEOAS configuration by hand either. I found this solution, which allowed me to use the HATEOAS configuration directly: https://stackoverflow.com/a/20364181/488932 – Robert Haines Jun 10 '17 at 10:24