/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.tests;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* Helper methods for web integration testing.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
public class TestMvcClient {
public static MediaType DEFAULT_MEDIA_TYPE = org.springframework.hateoas.MediaTypes.HAL_JSON;
private final MockMvc mvc;
private final LinkDiscoverers discoverers;
/**
* Creates a new {@link TestMvcClient} for the given {@link MockMvc} and {@link LinkDiscoverers}.
*
* @param mvc must not be {@literal null}.
* @param discoverers must not be {@literal null}.
*/
public TestMvcClient(MockMvc mvc, LinkDiscoverers discoverers) {
Assert.notNull(mvc, "MockMvc must not be null!");
Assert.notNull(discoverers, "LinkDiscoverers must not be null!");
this.mvc = mvc;
this.discoverers = discoverers;
}
/**
* Initializes web tests. Will register a {@link MockHttpServletRequest} for the current thread.
*/
public static void initWebTest() {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
public static void assertAllowHeaders(HttpEntity<?> response, HttpMethod... methods) {
HttpHeaders headers = response.getHeaders();
assertThat(headers.getAllow(), hasSize(methods.length));
assertThat(headers.getAllow(), hasItems(methods));
}
/**
* Perform GET [href] with an explicit Accept media type using MockMvc. Verify the requests succeeded and also came
* back as the Accept type.
*
* @param href
* @param contentType
* @return a mocked servlet response with results from GET [href]
* @throws Exception
*/
public MockHttpServletResponse request(String href, MediaType contentType) throws Exception {
return mvc.perform(get(href).accept(contentType)). //
andExpect(status().isOk()). //
andExpect(content().contentTypeCompatibleWith(contentType)). //
andReturn().getResponse();
}
/**
* Perform GET [href] with an explicit Accept media type using MockMvc. Verify the requests succeeded and also came
* back as the Accept type.
*
* @param href
* @param contentType
* @return a mocked servlet response with results from GET [href]
* @throws Exception
*/
public MockHttpServletResponse request(String href, MediaType contentType, HttpHeaders httpHeaders) throws Exception {
return mvc.perform(get(href).accept(contentType).headers(httpHeaders)). //
andExpect(status().isOk()). //
andExpect(content().contentType(contentType)). //
andReturn().getResponse();
}
/**
* Convenience wrapper that first expands the link using URI substitution before requesting with the default media
* type.
*
* @param link
* @return
* @throws Exception
*/
public MockHttpServletResponse request(Link link) throws Exception {
return request(link.expand().getHref());
}
/**
* Convenience wrapper that first expands the link using URI substitution and then GET [href] using an explicit media
* type
*
* @param link
* @param mediaType
* @return
* @throws Exception
*/
public MockHttpServletResponse request(Link link, MediaType mediaType) throws Exception {
return request(link.expand().getHref(), mediaType);
}
/**
* Convenience wrapper to GET [href] using the default media type.
*
* @param href
* @return
* @throws Exception
*/
public MockHttpServletResponse request(String href) throws Exception {
return request(href, DEFAULT_MEDIA_TYPE);
}
/**
* For a given link, expand the href using URI substitution and then do a simple GET.
*
* @param link
* @return
* @throws Exception
*/
public ResultActions follow(Link link) throws Exception {
return follow(link.expand().getHref());
}
/**
* Follow URL supplied as a string. NOTE: Assumes no URI templates.
*
* @param href
* @return
* @throws Exception
*/
public ResultActions follow(String href) throws Exception {
return follow(href, MediaType.ALL);
}
/**
* Folow Link with a specific Accept header (media type).
*
* @param link
* @param accept
* @return
* @throws Exception
*/
public ResultActions follow(Link link, MediaType accept) throws Exception {
return follow(link.expand().getHref(), accept);
}
/**
* Follow URL supplied as a string with a specific Accept header.
*
* @param href
* @param accept
* @return
* @throws Exception
*/
public ResultActions follow(String href, MediaType accept) throws Exception {
return mvc.perform(get(href).header(HttpHeaders.ACCEPT, accept.toString()));
}
/**
* Discover list of URIs associated with a rel, starting at the root node ("/")
*
* @param rel
* @return
* @throws Exception
*/
public List<Link> discover(String rel) throws Exception {
return discover(new Link("/"), rel);
}
/**
* Discover single URI associated with a rel, starting at the root node ("/")
*
* @param rel
* @return
* @throws Exception
*/
public Link discoverUnique(String rel) throws Exception {
List<Link> discover = discover(rel);
assertThat(discover, hasSize(1));
return discover.get(0);
}
/**
* Traverses the given link relations from the root.
*
* @param rels
* @return
* @throws Exception
*/
public Link discoverUnique(String... rels) throws Exception {
Iterator<String> toTraverse = Arrays.asList(rels).iterator();
Link lastLink = null;
while (toTraverse.hasNext()) {
String rel = toTraverse.next();
lastLink = lastLink == null ? discoverUnique(rel) : discoverUnique(lastLink, rel);
}
return lastLink;
}
/**
* Given a URI (root), discover the URIs for a given rel.
*
* @param root - URI to start from
* @param rel - name of the relationship to seek links
* @return list of {@link org.springframework.hateoas.Link Link} objects associated with the rel
* @throws Exception
*/
public List<Link> discover(Link root, String rel) throws Exception {
MockHttpServletResponse response = mvc.perform(get(root.expand().getHref()).accept(DEFAULT_MEDIA_TYPE)).//
andExpect(status().isOk()).//
andExpect(hasLinkWithRel(rel)).//
andReturn().getResponse();
String s = response.getContentAsString();
return getDiscoverer(response).findLinksWithRel(rel, s);
}
/**
* Given a URI (root), discover the unique URI for a given rel. NOTE: Assumes there is only one URI
*
* @param root
* @param rel
* @return {@link org.springframework.hateoas.Link Link} tied to a given rel
* @throws Exception
*/
public Link discoverUnique(Link root, String rel) throws Exception {
return discoverUnique(root, rel, DEFAULT_MEDIA_TYPE);
}
/**
* Given a URI (root), discover the unique URI for a given rel. NOTE: Assumes there is only one URI
*
* @param root the link to the resource to access.
* @param rel the link relation to discover in the response.
* @param mediaType the {@link MediaType} to request.
* @return {@link org.springframework.hateoas.Link Link} tied to a given rel
* @throws Exception
*/
public Link discoverUnique(Link root, String rel, MediaType mediaType) throws Exception {
MockHttpServletResponse response = mvc
.perform(get(root.expand().getHref())//
.accept(mediaType))
.andExpect(status().isOk())//
.andExpect(hasLinkWithRel(rel))//
.andReturn().getResponse();
return assertHasLinkWithRel(rel, response);
}
/**
* For a given servlet response, verify that the provided rel exists in its hypermedia. If so, return the URI link.
*
* @param rel
* @param response
* @return {@link org.springframework.hateoas.Link} of the rel found in the response
* @throws Exception
*/
public Link assertHasLinkWithRel(String rel, MockHttpServletResponse response) throws Exception {
String content = response.getContentAsString();
Link link = getDiscoverer(response).findLinkWithRel(rel, content);
assertThat("Expected to find link with rel " + rel + " but found none in " + content + "!", link,
is(notNullValue()));
return link;
}
/**
* MockMvc matcher used to verify existence of rel with URI link
*
* @param rel
* @return
*/
public ResultMatcher hasLinkWithRel(final String rel) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
MockHttpServletResponse response = result.getResponse();
String s = response.getContentAsString();
assertThat("Expected to find link with rel " + rel + " but found none in " + s, //
getDiscoverer(response).findLinkWithRel(rel, s), notNullValue());
}
};
}
/**
* Using the servlet response's content type, find the corresponding link discoverer.
*
* @param response
* @return {@link org.springframework.hateoas.LinkDiscoverer}
*/
public LinkDiscoverer getDiscoverer(MockHttpServletResponse response) {
String contentType = response.getContentType();
LinkDiscoverer linkDiscovererFor = discoverers.getLinkDiscovererFor(contentType);
assertThat("Did not find a LinkDiscoverer for returned media type " + contentType + "!", linkDiscovererFor,
is(notNullValue()));
return linkDiscovererFor;
}
}