Introduce support for HtmlUnit in Spring MVC Test

This commit introduces integration between MockMvc and HtmlUnit, thus
simplifying end-to-end testing when using HTML-based views and enabling
developers to do the following.

 - Easily test HTML pages using tools such as HtmlUnit, WebDriver, & Geb
   without the need to deploy to a Servlet container

 - Test JavaScript within pages

 - Optionally test using mock services to speed up testing

 - Share logic between in-container, end-to-end tests and
   out-of-container integration tests

Issue: SPR-13158
This commit is contained in:
Rob Winch
2015-06-23 11:31:48 -05:00
committed by Sam Brannen
parent c5a037a5cf
commit b73e39423c
28 changed files with 4123 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import org.springframework.util.Assert;
/**
* <p>
* Implementation of WebConnection that allows delegating to various WebConnection implementations. For example, if
* you host your JavaScript on the domain code.jquery.com, you might want to use the following:</p>
* <pre>
* WebClient webClient = new WebClient();
*
* MockMvc mockMvc = ...
* MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc);
* mockConnection.setWebClient(webClient);
*
* WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* WebConnection httpConnection = new HttpWebConnection(webClient);
* WebConnection webConnection = new DelegatingWebConnection(mockConnection, new DelegateWebConnection(cdnMatcher, httpConnection));
*
* webClient.setWebConnection(webConnection);
*
* WebClient webClient = new WebClient();
* webClient.setWebConnection(webConnection);
* </pre>
* @author Rob Winch
* @since 4.2
*/
public final class DelegatingWebConnection implements WebConnection {
private final List<DelegateWebConnection> connections;
private final WebConnection defaultConnection;
public DelegatingWebConnection(WebConnection defaultConnection, List<DelegateWebConnection> connections) {
Assert.notNull(defaultConnection, "defaultConnection cannot be null");
Assert.notEmpty(connections, "connections cannot be empty");
this.connections = connections;
this.defaultConnection = defaultConnection;
}
public DelegatingWebConnection(WebConnection defaultConnection,DelegateWebConnection... connections) {
this(defaultConnection, Arrays.asList(connections));
}
@Override
public WebResponse getResponse(WebRequest request) throws IOException {
for(DelegateWebConnection connection : connections) {
if(connection.getMatcher().matches(request)) {
return connection.getDelegate().getResponse(request);
}
}
return defaultConnection.getResponse(request);
}
public final static class DelegateWebConnection {
private final WebRequestMatcher matcher;
private final WebConnection delegate;
public DelegateWebConnection(WebRequestMatcher matcher, WebConnection delegate) {
this.matcher = matcher;
this.delegate = delegate;
}
private WebRequestMatcher getMatcher() {
return matcher;
}
private WebConnection getDelegate() {
return delegate;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
/**
* @author Rob Winch
* @since 4.2
*/
final class ForwardRequestPostProcessor implements RequestPostProcessor {
private final String forwardUrl;
public ForwardRequestPostProcessor(String url) {
Assert.hasText(url, "Forward url must have text");
forwardUrl = url;
}
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setServletPath(forwardUrl);
return request;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* <p>
* An implementation of WebRequestMatcher that allows matching on the host and optionally
* the port of WebRequest#getUrl(). For example, the following would match any request to
* the host "code.jquery.com" without regard for the port:
* </p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");
* </pre>
*
* Multiple hosts can also be passed in. For example, the following would match an request
* to the host "code.jquery.com" or the host "cdn.com" without regard for the port:
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com", "cdn.com");
* </pre>
*
* <p>
* Alternatively, one can also specify the port. For example, the following would match
* any request to the host "code.jquery.com" with the port of 80.
* </p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");
* </pre>
*
* <p>
* The above cdnMatcher would match: "http://code.jquery.com/jquery.js" (default port of
* 80) and "http://code.jquery.com:80/jquery.js". However, it would not match
* "https://code.jquery.com/jquery.js" (default port of 443).
* </p>
*
* @author Rob Winch
* @since 4.2
* @see UrlRegexRequestMatcher
* @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection
*/
public final class HostRequestMatcher implements WebRequestMatcher {
private final Set<String> hosts = new HashSet<String>();
/**
* Creates a new instance
*
* @param hosts the hosts to match on (i.e. "localhost", "example.com:443")
*/
public HostRequestMatcher(String... hosts) {
this.hosts.addAll(Arrays.asList(hosts));
}
@Override
public boolean matches(WebRequest request) {
URL url = request.getUrl();
String host = url.getHost();
if(hosts.contains(host)) {
return true;
}
int port = url.getPort();
if(port == -1) {
port = url.getDefaultPort();
}
String hostAndPort = host + ":" + port;
return hosts.contains(hostAndPort);
}
}

View File

@@ -0,0 +1,523 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.springframework.beans.Mergeable;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.SmartRequestBuilder;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* <p>
* Internal class used to allow a {@link WebRequest} into a {@link MockHttpServletRequest} using Spring MVC Test's
* {@link RequestBuilder}.
* </p>
* <p>
* By default the first path segment of the URL is used as the contextPath. To override this default see
* {@link #setContextPath(String)}.
* </p>
*
* @author Rob Winch
* @since 4.2
* @see MockMvcWebConnection
*/
final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private final Map<String, MockHttpSession> sessions;
private final WebClient webClient;
private final WebRequest webRequest;
private String contextPath;
private RequestBuilder parentBuilder;
private SmartRequestBuilder parentPostProcessor;
private RequestPostProcessor forwardPostProcessor;
/**
*
* @param sessions A {@link Map} of the {@link HttpSession#getId()} to currently managed {@link HttpSession}
* objects. Cannot be null.
* @param webClient the WebClient for retrieving cookies
* @param webRequest The {@link WebRequest} to transform into a {@link MockHttpServletRequest}. Cannot be null.
*/
public HtmlUnitRequestBuilder(Map<String, MockHttpSession> sessions, WebClient webClient,
WebRequest webRequest) {
Assert.notNull(sessions, "sessions cannot be null");
Assert.notNull(webClient, "webClient cannot be null");
Assert.notNull(webRequest, "webRequest cannot be null");
this.sessions = sessions;
this.webClient = webClient;
this.webRequest = webRequest;
}
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
String charset = getCharset();
String httpMethod = webRequest.getHttpMethod().name();
UriComponents uriComponents = uriComponents();
MockHttpServletRequest result = new HtmlUnitMockHttpServletRequest(servletContext, httpMethod,
uriComponents.getPath());
parent(result, parentBuilder);
result.setServerName(uriComponents.getHost()); // needs to be first for additional headers
authType(result);
result.setCharacterEncoding(charset);
content(result, charset);
contextPath(result, uriComponents);
contentType(result);
cookies(result);
headers(result);
locales(result);
servletPath(uriComponents, result);
params(result, uriComponents);
ports(uriComponents, result);
result.setProtocol("HTTP/1.1");
result.setQueryString(uriComponents.getQuery());
result.setScheme(uriComponents.getScheme());
pathInfo(uriComponents,result);
return postProcess(result);
}
private MockHttpServletRequest postProcess(MockHttpServletRequest request) {
if(parentPostProcessor != null) {
request = parentPostProcessor.postProcessRequest(request);
}
if(forwardPostProcessor != null) {
request = forwardPostProcessor.postProcessRequest(request);
}
return request;
}
private void parent(MockHttpServletRequest result, RequestBuilder parent) {
if(parent == null) {
return;
}
MockHttpServletRequest parentRequest = parent.buildRequest(result.getServletContext());
// session
HttpSession parentSession = parentRequest.getSession(false);
if(parentSession != null) {
Enumeration<String> attrNames = parentSession.getAttributeNames();
while(attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
Object attrValue = parentSession.getAttribute(attrName);
result.getSession().setAttribute(attrName, attrValue);
}
}
// header
Enumeration<String> headerNames = parentRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {
String attrName = headerNames.nextElement();
Enumeration<String> attrValues = parentRequest.getHeaders(attrName);
while(attrValues.hasMoreElements()) {
String attrValue = attrValues.nextElement();
result.addHeader(attrName, attrValue);
}
}
// parameter
Map<String, String[]> parentParams = parentRequest.getParameterMap();
for(Map.Entry<String,String[]> parentParam : parentParams.entrySet()) {
String paramName = parentParam.getKey();
String[] paramValues = parentParam.getValue();
result.addParameter(paramName, paramValues);
}
// cookie
Cookie[] parentCookies = parentRequest.getCookies();
if(parentCookies != null) {
result.setCookies(parentCookies);
}
// request attribute
Enumeration<String> parentAttrNames = parentRequest.getAttributeNames();
while(parentAttrNames.hasMoreElements()) {
String parentAttrName = parentAttrNames.nextElement();
result.setAttribute(parentAttrName, parentRequest.getAttribute(parentAttrName));
}
}
/**
* Sets the contextPath to be used. The value may be null in which case the first path segment of the URL is turned
* into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it
* can be empty string or it must start with a "/" and not end in a "/".
*
* @param contextPath A valid contextPath
* @throws IllegalArgumentException if contextPath is not a valid {@link HttpServletRequest#getContextPath()}.
*/
public void setContextPath(String contextPath) {
if (contextPath == null || "".equals(contextPath)) {
this.contextPath = contextPath;
return;
}
if (contextPath.endsWith("/")) {
throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'");
}
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'");
}
this.contextPath = contextPath;
}
public void setForwardPostProcessor(RequestPostProcessor postProcessor) {
this.forwardPostProcessor = postProcessor;
}
private void authType(MockHttpServletRequest request) {
String authorization = header("Authorization");
if (authorization != null) {
String[] authzParts = authorization.split(": ");
request.setAuthType(authzParts[0]);
}
}
private void content(MockHttpServletRequest result, String charset) {
String requestBody = webRequest.getRequestBody();
if (requestBody == null) {
return;
}
try {
result.setContent(requestBody.getBytes(charset));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private void contentType(MockHttpServletRequest result) {
String contentType = header("Content-Type");
result.setContentType(contentType == null ? MediaType.ALL_VALUE.toString() : contentType);
}
private void contextPath(MockHttpServletRequest result, UriComponents uriComponents) {
if (contextPath == null) {
List<String> pathSegments = uriComponents.getPathSegments();
if (pathSegments.isEmpty()) {
result.setContextPath("");
}
else {
result.setContextPath("/" + pathSegments.get(0));
}
}
else {
if (!uriComponents.getPath().startsWith(contextPath)) {
throw new IllegalArgumentException(uriComponents.getPath() + " should start with contextPath "
+ contextPath);
}
result.setContextPath(contextPath);
}
}
private void cookies(MockHttpServletRequest result) {
String cookieHeaderValue = header("Cookie");
Cookie[] parentCookies = result.getCookies();
List<Cookie> cookies = new ArrayList<Cookie>();
if (cookieHeaderValue != null) {
StringTokenizer tokens = new StringTokenizer(cookieHeaderValue, "=;");
while (tokens.hasMoreTokens()) {
String cookieName = tokens.nextToken().trim();
if (!tokens.hasMoreTokens()) {
throw new IllegalArgumentException("Expected value for cookie name " + cookieName
+ ". Full cookie was " + cookieHeaderValue);
}
String cookieValue = tokens.nextToken().trim();
processCookie(result, cookies, new Cookie(cookieName, cookieValue));
}
}
@SuppressWarnings("unchecked")
Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = webClient.getCookies(webRequest.getUrl());
for (com.gargoylesoftware.htmlunit.util.Cookie cookie : managedCookies) {
processCookie(result, cookies, new Cookie(cookie.getName(), cookie.getValue()));
}
if(parentCookies != null) {
for(Cookie cookie : parentCookies) {
cookies.add(cookie);
}
}
if (!cookies.isEmpty()) {
result.setCookies(cookies.toArray(new Cookie[0]));
}
}
private void processCookie(MockHttpServletRequest result, List<Cookie> cookies, Cookie cookie) {
cookies.add(cookie);
if ("JSESSIONID".equals(cookie.getName())) {
result.setRequestedSessionId(cookie.getValue());
result.setSession(httpSession(result, cookie.getValue()));
}
}
private String getCharset() {
String charset = webRequest.getCharset();
if (charset == null) {
return "ISO-8859-1";
}
return charset;
}
private String header(String headerName) {
return webRequest.getAdditionalHeaders().get(headerName);
}
private void headers(MockHttpServletRequest result) {
for (Entry<String, String> header : webRequest.getAdditionalHeaders().entrySet()) {
result.addHeader(header.getKey(), header.getValue());
}
}
private MockHttpSession httpSession(MockHttpServletRequest request, final String sessionid) {
MockHttpSession session;
synchronized (sessions) {
session = sessions.get(sessionid);
if (session == null) {
session = new HtmlUnitMockHttpSession(request, sessionid);
session.setNew(true);
synchronized (sessions) {
sessions.put(sessionid, session);
}
addSessionCookie(request, sessionid);
}
else {
session.setNew(false);
}
}
return session;
}
private void addSessionCookie(MockHttpServletRequest request, String sessionid) {
getCookieManager().addCookie(createCookie(request, sessionid));
}
private void removeSessionCookie(MockHttpServletRequest request, String sessionid) {
getCookieManager().removeCookie(createCookie(request, sessionid));
}
private com.gargoylesoftware.htmlunit.util.Cookie createCookie(MockHttpServletRequest request, String sessionid) {
return new com.gargoylesoftware.htmlunit.util.Cookie(request.getServerName(), "JSESSIONID", sessionid,
request.getContextPath() + "/", null, request.isSecure(), true);
}
private void locales(MockHttpServletRequest result) {
String locale = header("Accept-Language");
if (locale == null) {
result.addPreferredLocale(Locale.getDefault());
}
else {
String[] locales = locale.split(", ");
for (int i = locales.length - 1; i >= 0; i--) {
result.addPreferredLocale(parseLocale(locales[i]));
}
}
}
private void params(MockHttpServletRequest result, UriComponents uriComponents) {
for (Entry<String, List<String>> values : uriComponents.getQueryParams().entrySet()) {
String name = values.getKey();
for (String value : values.getValue()) {
try {
result.addParameter(name, URLDecoder.decode(value, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
for (NameValuePair param : webRequest.getRequestParameters()) {
result.addParameter(param.getName(), param.getValue());
}
}
private Locale parseLocale(String locale) {
Matcher matcher = LOCALE_PATTERN.matcher(locale);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid locale " + locale);
}
String language = matcher.group(1);
String country = matcher.group(2);
if (country == null) {
country = "";
}
String qualifier = matcher.group(3);
if (qualifier == null) {
qualifier = "";
}
return new Locale(language, country, qualifier);
}
private void pathInfo(UriComponents uriComponents, MockHttpServletRequest result) {
result.setPathInfo(null);
}
private void servletPath(MockHttpServletRequest result, String requestPath) {
String servletPath = requestPath.substring(result.getContextPath().length());
if ("".equals(servletPath)) {
servletPath = null;
}
result.setServletPath(servletPath);
}
private void servletPath(UriComponents uriComponents, MockHttpServletRequest result) {
if ("".equals(result.getPathInfo())) {
result.setPathInfo(null);
}
servletPath(result, uriComponents.getPath());
}
private void ports(UriComponents uriComponents, MockHttpServletRequest result) {
int serverPort = uriComponents.getPort();
result.setServerPort(serverPort);
if (serverPort == -1) {
int portConnection = webRequest.getUrl().getDefaultPort();
result.setLocalPort(serverPort);
result.setRemotePort(portConnection);
}
else {
result.setRemotePort(serverPort);
}
}
private UriComponents uriComponents() {
URL url = webRequest.getUrl();
UriComponentsBuilder uriBldr = UriComponentsBuilder.fromUriString(url.toExternalForm());
return uriBldr.build();
}
@Override
public boolean isMergeEnabled() {
return true;
}
@Override
public Object merge(Object parent) {
if (parent == null) {
return this;
}
if(parent instanceof RequestBuilder) {
this.parentBuilder = (RequestBuilder) parent;
}
if (parent instanceof SmartRequestBuilder) {
this.parentPostProcessor = (SmartRequestBuilder) parent;
}
return this;
}
/**
* An extension to {@link MockHttpServletRequest} that ensures that when a new {@link HttpSession} is created, it is
* added to the managed sessions.
*
* @author Rob Winch
*/
private final class HtmlUnitMockHttpServletRequest extends MockHttpServletRequest {
private HtmlUnitMockHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
super(servletContext, method, requestURI);
}
public HttpSession getSession(boolean create) {
HttpSession result = super.getSession(false);
if (result == null && create) {
HtmlUnitMockHttpSession newSession = new HtmlUnitMockHttpSession(this);
setSession(newSession);
newSession.setNew(true);
String sessionid = newSession.getId();
synchronized (sessions) {
sessions.put(sessionid, newSession);
}
addSessionCookie(this, sessionid);
result = newSession;
}
return result;
}
public HttpSession getSession() {
return super.getSession();
}
public void setSession(HttpSession session) {
super.setSession(session);
}
}
/**
* An extension to {@link MockHttpSession} that ensures when {@link #invalidate()} is called that the
* {@link HttpSession} is removed from the managed sessions.
*
* @author Rob Winch
*/
private final class HtmlUnitMockHttpSession extends MockHttpSession {
private final MockHttpServletRequest request;
private HtmlUnitMockHttpSession(MockHttpServletRequest request) {
super(request.getServletContext());
this.request = request;
}
private HtmlUnitMockHttpSession(MockHttpServletRequest request, String id) {
super(request.getServletContext(), id);
this.request = request;
}
public void invalidate() {
super.invalidate();
synchronized (sessions) {
sessions.remove(getId());
}
removeSessionCookie(request, getId());
}
}
private CookieManager getCookieManager() {
return webClient.getCookieManager();
}
private static final Pattern LOCALE_PATTERN = Pattern.compile("^\\s*(\\w{2})(?:-(\\w{2}))?(?:;q=(\\d+\\.\\d+))?$");
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import com.gargoylesoftware.htmlunit.WebClient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* Simplifies creating a WebClient that delegates to a MockMvc instance.
*
* @author Rob Winch
* @since 4.2
*/
public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcWebClientBuilder> {
protected MockMvcWebClientBuilder(MockMvc mockMvc) {
super(mockMvc);
}
protected MockMvcWebClientBuilder(WebApplicationContext context) {
super(context);
}
protected MockMvcWebClientBuilder(WebApplicationContext context, MockMvcConfigurer configurer) {
super(context, configurer);
}
/**
* Creates a new instance with a WebApplicationContext.
*
* @param context the WebApplicationContext to use. Cannot be null.
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context) {
return new MockMvcWebClientBuilder(context);
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
* @return the MockMvcWebClientBuilder to use
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) {
return new MockMvcWebClientBuilder(context, configurer);
}
/**
* Creates a new instance with a MockMvc instance.
*
* @param mockMvc the MockMvc to use. Cannot be null.
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder mockMvcSetup(MockMvc mockMvc) {
return new MockMvcWebClientBuilder(mockMvc);
}
/**
* Creates a WebClient that uses the provided MockMvc for any matching requests and a
* WebClient with all the default settings for any other request.
*
* @return the WebClient to use
*/
public WebClient createWebClient() {
return configureWebClient(new WebClient());
}
/**
* Creates a WebClient that uses the provided MockMvc for any matching requests and the
* provided WebClient for any other request.
*
* @param webClient The WebClient to delegate to for requests that do not match. Cannot be null.
*
* @return the WebClient to use
*/
public WebClient configureWebClient(WebClient webClient) {
webClient.setWebConnection(createConnection(webClient.getWebConnection()));
return webClient;
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
import org.springframework.util.Assert;
/**
* <p>
* Allows {@link MockMvc} to transform a {@link WebRequest} into a {@link WebResponse}. This is the core integration
* with <a href="http://htmlunit.sourceforge.net/">HTML Unit</a>.
* </p>
* <p>
* Example usage can be seen below:
* </p>
*
* <pre>
* WebClient webClient = new WebClient();
* MockMvc mockMvc = ...
* MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc);
* mockConnection.setWebClient(webClient);
* webClient.setWebConnection(webConnection);
*
* ... use webClient as normal ...
* </pre>
*
* @author Rob Winch
* @since 4.2
* @see WebConnectionHtmlUnitDriver
*/
public final class MockMvcWebConnection implements WebConnection {
private WebClient webClient;
private final Map<String, MockHttpSession> sessions = new HashMap<String, MockHttpSession>();
private final MockMvc mockMvc;
private final String contextPath;
/**
* Creates a new instance that assumes the context root of the application is "". For example,
* the URL http://localhost/test/this would use "" as the context root.
*
* @param mockMvc the MockMvc instance to use
*/
public MockMvcWebConnection(MockMvc mockMvc) {
this(mockMvc, "");
}
/**
* Creates a new instance with a specified context root.
*
* @param mockMvc the MockMvc instance to use
* @param contextPath the contextPath to use. The value may be null in which case the first path segment of the URL is turned
* into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it
* can be empty string or it must start with a "/" and not end in a "/".
*/
public MockMvcWebConnection(MockMvc mockMvc, String contextPath) {
Assert.notNull(mockMvc, "mockMvc cannot be null");
validateContextPath(contextPath);
this.webClient = new WebClient();
this.mockMvc = mockMvc;
this.contextPath = contextPath;
}
public WebResponse getResponse(WebRequest webRequest) throws IOException {
long startTime = System.currentTimeMillis();
HtmlUnitRequestBuilder requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
requestBuilder.setContextPath(contextPath);
MockHttpServletResponse httpServletResponse = getResponse(requestBuilder);
String forwardedUrl = httpServletResponse.getForwardedUrl();
while(forwardedUrl != null) {
requestBuilder.setForwardPostProcessor(new ForwardRequestPostProcessor(forwardedUrl));
httpServletResponse = getResponse(requestBuilder);
forwardedUrl = httpServletResponse.getForwardedUrl();
}
return new MockWebResponseBuilder(startTime, webRequest, httpServletResponse).build();
}
public void setWebClient(WebClient webClient) {
Assert.notNull(webClient, "webClient cannot be null");
this.webClient = webClient;
}
private CookieManager getCookieManager() {
return webClient.getCookieManager();
}
private MockHttpServletResponse getResponse(RequestBuilder requestBuilder) throws IOException {
ResultActions resultActions;
try {
resultActions = mockMvc.perform(requestBuilder);
}
catch (Exception e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
return resultActions.andReturn().getResponse();
}
/**
* Performs validation on the contextPath
*
* @param contextPath the contextPath to validate
*/
private static void validateContextPath(String contextPath) {
if (contextPath == null || "".equals(contextPath)) {
return;
}
if (contextPath.endsWith("/")) {
throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'");
}
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'");
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.util.ArrayList;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
* Makes it easy to create a WebConnection that uses MockMvc and optionally delegates to
* a real WebConnection for specific requests. The default is to use MockMvc for any host
* that is "localhost" and otherwise use a real WebConnection.
*
* @author Rob Winch
* @since 4.2
*/
public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebConnectionBuilderSupport<T>> {
private String contextPath = "";
private final MockMvc mockMvc;
private List<WebRequestMatcher> mockMvcRequestMatchers = new ArrayList<WebRequestMatcher>();
private boolean alwaysUseMockMvc;
/**
* Creates a new instance using a MockMvc instance
*
* @param mockMvc the MockMvc instance to use. Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) {
Assert.notNull(mockMvc, "mockMvc cannot be null");
this.mockMvc = mockMvc;
this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost"));
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context) {
this(MockMvcBuilders.webAppContextSetup(context).build());
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context, MockMvcConfigurer configurer) {
this(MockMvcBuilders.webAppContextSetup(context).apply(configurer).build());
}
/**
* The context path to use. Default is "". If the value is null, then the first path
* segment of the request URL is assumed to be the context path.
*
* @param contextPath the context path to use.
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T contextPath(String contextPath) {
this.contextPath = contextPath;
return (T) this;
}
/**
* Always use MockMvc no matter what the request looks like.
*
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T alwaysUseMockMvc() {
this.alwaysUseMockMvc = true;
return (T) this;
}
/**
* Add additional WebRequestMatcher instances that if return true will ensure MockMvc
* is used.
*
* @param matchers the WebRequestMatcher instances that if true will ensure MockMvc
* processes the request.
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvc(WebRequestMatcher... matchers) {
for(WebRequestMatcher matcher : matchers) {
this.mockMvcRequestMatchers.add(matcher);
}
return (T) this;
}
/**
* Add additional WebRequestMatcher instances that will return true if the host matches.
*
* @param hosts the additional hosts that will ensure MockMvc gets invoked (i.e. example.com or example.com:8080).
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvcForHosts(String... hosts) {
this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts));
return (T) this;
}
/**
* Creates a new WebConnection that will use a MockMvc instance if one of the
* specified WebRequestMatcher matches.
*
* @param defaultConnection the default WebConnection to use if none of the specified
* WebRequestMatcher instances match. Cannot be null.
* @return a new WebConnection that will use a MockMvc instance if one of the
* specified WebRequestMatcher matches.
*
* @see #alwaysUseMockMvc
* @see #useMockMvc(WebRequestMatcher...)
* @see #useMockMvcForHosts(String...)
*/
protected final WebConnection createConnection(WebConnection defaultConnection) {
Assert.notNull(defaultConnection, "defaultConnection cannot be null");
MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(mockMvc, contextPath);
if(alwaysUseMockMvc) {
return mockMvcWebConnection;
}
List<DelegatingWebConnection.DelegateWebConnection> delegates = new ArrayList<DelegatingWebConnection.DelegateWebConnection>(mockMvcRequestMatchers.size());
for(WebRequestMatcher matcher : mockMvcRequestMatchers) {
delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection));
}
return new DelegatingWebConnection(defaultConnection, delegates);
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebResponseData;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.Assert;
/**
* @author Rob Winch
* @since 4.2
*/
final class MockWebResponseBuilder {
private final long startTime;
private final WebRequest webRequest;
private final MockHttpServletResponse response;
public MockWebResponseBuilder(long startTime, WebRequest webRequest, MockHttpServletResponse httpServletResponse) {
Assert.notNull(webRequest, "webRequest");
Assert.notNull(httpServletResponse, "httpServletResponse cannot be null");
this.startTime = startTime;
this.webRequest = webRequest;
this.response = httpServletResponse;
}
public WebResponse build() throws IOException {
WebResponseData webResponseData = webResponseData();
long endTime = System.currentTimeMillis();
return new WebResponse(webResponseData, webRequest, endTime - startTime);
}
private WebResponseData webResponseData() throws IOException {
List<NameValuePair> responseHeaders = responseHeaders();
int statusCode = response.getRedirectedUrl() == null ? response.getStatus() : 301;
String statusMessage = statusMessage(statusCode);
return new WebResponseData(response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders);
}
private String statusMessage(int statusCode) {
String errorMessage = response.getErrorMessage();
if (errorMessage != null) {
return errorMessage;
}
try {
return HttpStatus.valueOf(statusCode).getReasonPhrase();
}
catch (IllegalArgumentException useDefault) {
}
;
return "N/A";
}
private List<NameValuePair> responseHeaders() {
Collection<String> headerNames = response.getHeaderNames();
List<NameValuePair> responseHeaders = new ArrayList<NameValuePair>(headerNames.size());
for (String headerName : headerNames) {
List<Object> headerValues = response.getHeaderValues(headerName);
for (Object value : headerValues) {
responseHeaders.add(new NameValuePair(headerName, String.valueOf(value)));
}
}
String location = response.getRedirectedUrl();
if (location != null) {
responseHeaders.add(new NameValuePair("Location", location));
}
return responseHeaders;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import java.util.regex.Pattern;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* <p>
* An implementation of WebRequestMatcher that allows matching on WebRequest#getUrl().toExternalForm() using a regular expression. For example, if you would like to match on the domain code.jquery.com, you might want to use the following:</p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* </pre>
*
* @author Rob Winch
* @since 4.2
* @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection
*/
public final class UrlRegexRequestMatcher implements WebRequestMatcher {
private Pattern pattern;
public UrlRegexRequestMatcher(String regex) {
pattern = Pattern.compile(regex);
}
public UrlRegexRequestMatcher(Pattern pattern) {
this.pattern = pattern;
}
@Override
public boolean matches(WebRequest request) {
String url = request.getUrl().toExternalForm();
return pattern.matcher(url).matches();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* Strategy to match on a WebRequest
*
* @author Rob Winch
* @since 4.2
*/
public interface WebRequestMatcher {
/**
* Return true if matches on WebRequest, else false
*
* @param request the WebRequest to attempt to match on
* @return true if matches on WebRequest, else false
*/
boolean matches(WebRequest request);
}

View File

@@ -0,0 +1,7 @@
/**
* Support for MockMvc and HtmlUnit integration
*
* @author Rob Winch
* @since 4.2
*/
package org.springframework.test.web.servlet.htmlunit;

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnectionBuilderSupport;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* Convenience class for building an HtmlUnitDriver that will delegate to MockMvc and
* optionally delegate to an actual connection for specific requests.
*
* By default localhost will delegate to MockMvc and any other URL will delegate
*
* @author Rob Winch
* @since 4.2
*/
public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcHtmlUnitDriverBuilder> {
private boolean javascriptEnabled = true;
protected MockMvcHtmlUnitDriverBuilder(MockMvc mockMvc) {
super(mockMvc);
}
protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context) {
super(context);
}
protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context, MockMvcConfigurer configurer) {
super(context, configurer);
}
/**
* Creates a new instance with a WebApplicationContext.
*
* @param context the WebApplicationContext to use. Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context) {
return new MockMvcHtmlUnitDriverBuilder(context);
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) {
return new MockMvcHtmlUnitDriverBuilder(context, configurer);
}
/**
* Creates a new instance with a MockMvc instance.
*
* @param mockMvc the MockMvc to use. Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder mockMvcSetup(MockMvc mockMvc) {
return new MockMvcHtmlUnitDriverBuilder(mockMvc);
}
/**
* Specifies if JavaScript should be enabled or not. Default is true.
*
* @param javascriptEnabled if JavaScript should be enabled or not.
* @return the builder for further customizations
*/
public MockMvcHtmlUnitDriverBuilder javascriptEnabled(boolean javascriptEnabled) {
this.javascriptEnabled = javascriptEnabled;
return this;
}
/**
* Creates a new HtmlUnitDriver with the BrowserVersion set to CHROME. For additional
* configuration options, use configureDriver.
*
* @return the HtmlUnitDriver to use
* @see #configureDriver(WebConnectionHtmlUnitDriver)
*/
public HtmlUnitDriver createDriver() {
return configureDriver(new WebConnectionHtmlUnitDriver(BrowserVersion.CHROME));
}
/**
* Configures an existing WebConnectionHtmlUnitDriver.
*
* @param driver the WebConnectionHtmlUnitDriver to configure
* @return the HtmlUnitDriver to use
*/
public HtmlUnitDriver configureDriver(WebConnectionHtmlUnitDriver driver) {
driver.setJavascriptEnabled(javascriptEnabled);
driver.setWebConnection(createConnection(driver.getWebConnection()));
return driver;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2015 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.test.web.servlet.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.util.Assert;
/**
* <p>
* Allows configuring the WebConnection for an HtmlUnitDriver instance. This is useful
* because it allows a MockMvcWebConnection to be injected.
* </p>
*
* @author Rob Winch
* @since 4.2
* @see MockMvcHtmlUnitDriverBuilder
*/
public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver {
private WebClient webClient;
public WebConnectionHtmlUnitDriver(BrowserVersion version) {
super(version);
}
public WebConnectionHtmlUnitDriver() {
}
public WebConnectionHtmlUnitDriver(boolean enableJavascript) {
super(enableJavascript);
}
public WebConnectionHtmlUnitDriver(Capabilities capabilities) {
super(capabilities);
}
/**
* Captures the WebClient that is used so that its WebConnection is accessible.
*
* @param client The client to modify
* @return The modified client
*/
@Override
protected final WebClient modifyWebClient(WebClient client) {
webClient = super.modifyWebClient(client);
webClient = configureWebClient(webClient);
return webClient;
}
/**
* Subclasses can override this method to customise the WebClient that the HtmlUnit
* driver uses.
*
* @param client The client to modify
* @return The modified client
*/
protected WebClient configureWebClient(WebClient client) {
return client;
}
/**
* Allows accessing the current WebConnection
*
* @return the current WebConnection
*/
public WebConnection getWebConnection() {
return webClient.getWebConnection();
}
/**
* Sets the WebConnection to be used.
*
* @param webConnection the WebConnection to use. Cannot be null.
*/
public void setWebConnection(WebConnection webConnection) {
Assert.notNull(webConnection, "webConnection cannot be null");
this.webClient.setWebConnection(webConnection);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Support for MockMvc and HtmlUnitDriver
*
* @author Rob Winch
* @since 4.2
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;