7

After creating a resource in Spring REST Controller , I am returning it's location in header as below.

@RequestMapping(..., method = RequestMethod.POST)
public ResponseEntity<Void> createResource(..., UriComponentsBuilder ucb) {

    ...

    URI locationUri = ucb.path("/the/resources/")
        .path(someId)
        .build()
        .toUri();

    return ResponseEntity.created(locationUri).build();
}

In Unit Test, I am checking its location as below.

@Test
public void testCreateResource(...) {
    ...
    MockHttpServletRequestBuilder request = post("...")
        .content(...)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON);

    request.session(sessionMocked);

    mvc.perform(request)
        .andExpect(status().isCreated())
        .andExpect(header().string("Location", "/the/resources" + id);
}

This result cases fails with following message.

java.lang.AssertionError: Response header Location expected:</the/resources/123456> but was:<http://localhost/the/resources/123456>

Seems like I have to provide context prefix http://localhost for Location header in expectation.

  • Is it safe to hard code context? If so, why?
  • If not, what is right way to generate it correctly for test case?
Suraj Bajaj
  • 6,630
  • 5
  • 34
  • 49
Bilal Mirza
  • 2,576
  • 4
  • 30
  • 57

3 Answers3

7

I'm guessing because you are using UriComponentsBuilder to build your URI it's setting the host name in your location header. Had you used something like just new URI("/the/resources"), your test would have passed.

In your case, I'd use redirectedUrlPattern to match the redirection URL:

.andExpect(redirectedUrlPattern("http://*/the/resources"))

This will match any hostname, so you don't have to hardcode localhost. Learn more about different patterns that you can use with AntPathMatcherhere.

Suraj Bajaj
  • 6,630
  • 5
  • 34
  • 49
  • 3
    Your solution tests for a redirect behavior. The OP wanted a location header tested. I must admit I didn't know about the `redirectedUrlPattern` until today – Julius Krah Jul 23 '17 at 20:46
3

If you don't need to have a full URI in the Location header on the response (i.e. no requirement, design constraint etc...): Consider switching to use a relative URI ( which is valid from HTTP standards perspective - see [1]: https://www.rfc-editor.org/rfc/rfc7231 ) Relative URIs is a proposed standard that is supported by modern browsers and libraries. This will allow you to test the behavior of the endpoint and make it less fragile in the long run.

If you need to assert the full path, since you are using MockMvc, you can set the uri in the test request to exactly what you want:

@Autowired
private WebApplicationContext webApplicationContext;

@Test
public void testCreateResource() {
    MockMvc mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    mvc.perform(MockMvcRequestBuilders.get(new URI("http://testserver/the/resources")));

This will make the injected builder produce "http://testserver" when build is called. Note of caution, a framework change in the future could cause you headaches if they remove this test behavior.

Community
  • 1
  • 1
Bob Lukens
  • 700
  • 6
  • 10
1

Facing the same problem, I tried out the solution Suraj Bajaj provided, and it worked fine for me.

I then took a try on asserting the text of the header field "Location" directly and ended up using a containsString().

This simple solution should also be a feasible alternative, as long as server context and the question of relative/absolute path are not of interest:

mvc.perform(request)
  .andExpect(status().isCreated())
  .andExpect(header().string("Location", containsString("/the/resources"+id)));
Daniel Sixl
  • 2,488
  • 2
  • 16
  • 29