org.apache.wicket.util.tester.BaseWicketTester Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.util.tester;
import static junit.framework.Assert.*;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import junit.framework.AssertionFailedError;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IPageManagerProvider;
import org.apache.wicket.IPageRendererProvider;
import org.apache.wicket.IRequestCycleProvider;
import org.apache.wicket.IRequestListener;
import org.apache.wicket.IResourceListener;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.RequestListenerInterface;
import org.apache.wicket.Session;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.FeedbackMessages;
import org.apache.wicket.feedback.IFeedbackMessageFilter;
import org.apache.wicket.markup.ContainerInfo;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.Markup;
import org.apache.wicket.markup.MarkupParser;
import org.apache.wicket.markup.MarkupResourceStream;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IFormSubmitListener;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ILinkListener;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.link.ResourceLink;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.parser.XmlPullParser;
import org.apache.wicket.markup.parser.XmlTag;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.mock.MockPageManager;
import org.apache.wicket.mock.MockRequestParameters;
import org.apache.wicket.page.IPageManager;
import org.apache.wicket.page.IPageManagerContext;
import org.apache.wicket.protocol.http.IMetaDataBufferingWebResponse;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WicketFilter;
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
import org.apache.wicket.protocol.http.mock.MockHttpServletResponse;
import org.apache.wicket.protocol.http.mock.MockHttpSession;
import org.apache.wicket.protocol.http.mock.MockServletContext;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import org.apache.wicket.protocol.http.servlet.ServletWebResponse;
import org.apache.wicket.request.IExceptionMapper;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.cycle.RequestCycleContext;
import org.apache.wicket.request.handler.BookmarkablePageRequestHandler;
import org.apache.wicket.request.handler.IPageProvider;
import org.apache.wicket.request.handler.ListenerInterfaceRequestHandler;
import org.apache.wicket.request.handler.PageAndComponentProvider;
import org.apache.wicket.request.handler.PageProvider;
import org.apache.wicket.request.handler.RenderPageRequestHandler;
import org.apache.wicket.request.handler.render.PageRenderer;
import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.session.ISessionStore.UnboundListener;
import org.apache.wicket.settings.IRequestCycleSettings.RenderStrategy;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A helper class to ease unit testing of Wicket applications without the need for a servlet
* container. See javadoc of WicketTester
for example usage. This class can be used as
* is, but JUnit users should use derived class WicketTester
.
*
* @see WicketTester
*
* @author Ingram Chen
* @author Juergen Donnerstag
* @author Frank Bille
* @author Igor Vaynberg
*
* @since 1.2.6
*/
public class BaseWicketTester
{
/** log. */
private static final Logger log = LoggerFactory.getLogger(BaseWicketTester.class);
private final ServletContext servletContext;
private MockHttpSession httpSession;
private final WebApplication application;
private boolean followRedirects = true;
private int redirectCount;
private MockHttpServletRequest lastRequest;
private MockHttpServletResponse lastResponse;
private final List previousRequests = Generics.newArrayList();
private final List previousResponses = Generics.newArrayList();
/** current request and response */
private MockHttpServletRequest request;
private MockHttpServletResponse response;
/** current session */
private Session session;
/** current request cycle */
private RequestCycle requestCycle;
private Page lastRenderedPage;
private boolean exposeExceptions = true;
private boolean useRequestUrlAsBase = true;
private IRequestHandler forcedHandler;
// Simulates the cookies maintained by the browser
private final List browserCookies = Generics.newArrayList();
private ComponentInPage componentInPage;
// User may provide request header value any time. They get applied (and reset) upon next
// invocation of processRequest()
private Map preHeader;
/**
* The method that is used to create the {@link ServletWebRequest} from the provided
* {@link WebApplication}.
*/
// TODO Wicket 1.6 - make WebApplication.newWebRequest() somehow visible for BaseWicketTester
// to avoid the usage of reflection
private Method newWebRequestMethod = null;
/**
* Creates WicketTester
and automatically create a WebApplication
, but
* the tester will have no home page.
*/
public BaseWicketTester()
{
this(new MockApplication());
}
/**
* Creates WicketTester
and automatically creates a WebApplication
.
*
* @param
* @param homePage
* a home page Class
*/
public BaseWicketTester(final Class homePage)
{
this(new MockApplication()
{
@Override
public Class extends Page> getHomePage()
{
return homePage;
}
});
}
/**
* Creates a WicketTester
.
*
* @param application
* a WicketTester
WebApplication
object
*/
public BaseWicketTester(final WebApplication application)
{
this(application, (MockServletContext)null);
}
/**
* Creates a WicketTester
.
*
* @param application
* a WicketTester
WebApplication
object
* @param servletContextBasePath
* the absolute path on disk to the web application's contents (e.g. war root) - may
* be null
*/
public BaseWicketTester(final WebApplication application, String servletContextBasePath)
{
this(application, new MockServletContext(application, servletContextBasePath));
}
/**
* Creates a WicketTester
.
*
* @param application
* a WicketTester
WebApplication
object
* @param servletCtx
* the servlet context used as backend
*/
public BaseWicketTester(final WebApplication application, final ServletContext servletCtx)
{
servletContext = servletCtx != null ? servletCtx
: new MockServletContext(application, null);
final FilterConfig filterConfig = new TestFilterConfig();
WicketFilter filter = new WicketFilter()
{
@Override
public FilterConfig getFilterConfig()
{
return filterConfig;
}
};
application.setWicketFilter(filter);
httpSession = new MockHttpSession(servletContext);
ThreadContext.detach();
this.application = application;
// FIXME some tests are leaking applications by not calling destroy on them or overriding
// teardown() without calling super, for now we work around by making each name unique
application.setName("WicketTesterApplication-" + UUID.randomUUID());
ThreadContext.setApplication(application);
application.setServletContext(servletContext);
// initialize the application
application.initApplication();
// We don't expect any changes during testing. In addition we avoid creating
// ModificationWatcher threads tests.
application.getResourceSettings().setResourcePollFrequency(getResourcePollFrequency());
// reconfigure application for the test environment
application.setPageRendererProvider(new LastPageRecordingPageRendererProvider(
application.getPageRendererProvider()));
application.setRequestCycleProvider(new TestRequestCycleProvider(
application.getRequestCycleProvider()));
IPageManagerProvider pageManagerProvider = newTestPageManagerProvider();
if (pageManagerProvider != null)
{
application.setPageManagerProvider(pageManagerProvider);
}
// create a new session when the old one is invalidated
application.getSessionStore().registerUnboundListener(new UnboundListener()
{
public void sessionUnbound(String sessionId)
{
newSession();
}
});
// prepare session
setupNextRequestCycle();
}
/**
* By default Modification Watcher is disabled by default for the tests.
*
* @return the duration between two checks for changes in the resources
*/
protected Duration getResourcePollFrequency()
{
return null;
}
/**
*
* @return page manager provider
*/
protected IPageManagerProvider newTestPageManagerProvider()
{
return new TestPageManagerProvider();
}
/**
* @return last rendered page
*/
public Page getLastRenderedPage()
{
return lastRenderedPage;
}
/**
*
*/
private void setupNextRequestCycle()
{
request = new MockHttpServletRequest(application, httpSession, servletContext);
request.setURL(request.getContextPath() + request.getServletPath() + "/");
// assign protocol://host:port to next request unless the last request was ajax
final boolean assignBaseLocation = lastRequest != null &&
lastRequest.getHeader("Wicket-Ajax") == null;
// resume request processing with scheme://host:port from last request
if (assignBaseLocation)
{
request.setScheme(lastRequest.getScheme());
request.setSecure(lastRequest.isSecure());
request.setServerName(lastRequest.getServerName());
request.setServerPort(lastRequest.getServerPort());
}
transferCookies();
response = new MockHttpServletResponse(request);
ServletWebRequest servletWebRequest = newServletWebRequest();
requestCycle = application.createRequestCycle(servletWebRequest,
newServletWebResponse(servletWebRequest));
requestCycle.setCleanupFeedbackMessagesOnDetach(false);
ThreadContext.setRequestCycle(requestCycle);
if (session == null)
{
newSession();
}
}
/**
* Copies all cookies with a positive age from the last response to the request that is going to
* be used for the next cycle.
*/
private void transferCookies()
{
if (lastResponse != null)
{
List cookies = lastResponse.getCookies();
if (cookies != null)
{
for (Cookie cookie : cookies)
{
if (cookie.getMaxAge() > 0)
{
request.addCookie(cookie);
}
}
}
}
}
/**
* @param servletWebRequest
* @return servlet web response
*/
protected Response newServletWebResponse(final ServletWebRequest servletWebRequest)
{
return new WicketTesterServletWebResponse(servletWebRequest, response);
}
/**
* @return the configured in the user's application web request
*/
private ServletWebRequest newServletWebRequest()
{
if (newWebRequestMethod == null)
{
try
{
newWebRequestMethod = WebApplication.class.getDeclaredMethod("newWebRequest",
new Class[] { HttpServletRequest.class, String.class });
newWebRequestMethod.setAccessible(true);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
ServletWebRequest webRequest;
try
{
webRequest = (ServletWebRequest)newWebRequestMethod.invoke(application, request,
request.getFilterPrefix());
}
catch (Exception x)
{
throw new RuntimeException(x);
}
return webRequest;
}
/**
*
*/
private void newSession()
{
ThreadContext.setSession(null);
// the following will create a new session and put it in the thread context
session = Session.get();
}
/**
* @return request object
*/
public MockHttpServletRequest getRequest()
{
return request;
}
/**
* @param request
*/
public void setRequest(final MockHttpServletRequest request)
{
this.request = request;
applyRequest();
}
/**
* @return session
*/
public Session getSession()
{
return session;
}
/**
* Returns {@link HttpSession} for this environment
*
* @return session
*/
public MockHttpSession getHttpSession()
{
return httpSession;
}
/**
* Returns the {@link Application} for this environment.
*
* @return application
*/
public WebApplication getApplication()
{
return application;
}
/**
* Returns the {@link ServletContext} for this environment
*
* @return servlet context
*/
public ServletContext getServletContext()
{
return servletContext;
}
/**
* Destroys the tester. Restores {@link ThreadContext} to state before instance of
* {@link WicketTester} was created.
*/
public void destroy()
{
application.internalDestroy();
ThreadContext.detach();
}
/**
* @return true, if process was executed successfully
*/
public boolean processRequest()
{
return processRequest(null, null);
}
/**
* Processes the request in mocked Wicket environment.
*
* @param request
* request to process
* @return true, if process was executed successfully
*/
public boolean processRequest(final MockHttpServletRequest request)
{
return processRequest(request, null);
}
/**
* Processes the request in mocked Wicket environment.
*
* @param request
* request to process
* @param forcedRequestHandler
* optional parameter to override parsing the request URL and force
* {@link IRequestHandler}
* @return true, if process was executed successfully
*/
public boolean processRequest(final MockHttpServletRequest request,
final IRequestHandler forcedRequestHandler)
{
return processRequest(request, forcedRequestHandler, false);
}
/**
* @param forcedRequestHandler
* @return true, if process was executed successfully
*/
public boolean processRequest(final IRequestHandler forcedRequestHandler)
{
return processRequest(null, forcedRequestHandler, false);
}
/**
* Process the request. This is a fairly central function and is almost always invoked for
* executing the request.
*
* You may subclass processRequest it, to monitor or change any pre-configured value. Request
* headers can be configured more easily by calling {@link #addRequestHeader(String, String)}.
*
* @param forcedRequest
* Can be null.
* @param forcedRequestHandler
* Can be null.
* @param redirect
* @return true, if process was executed successfully
*/
protected boolean processRequest(final MockHttpServletRequest forcedRequest,
final IRequestHandler forcedRequestHandler, final boolean redirect)
{
if (forcedRequest != null)
{
request = forcedRequest;
}
forcedHandler = forcedRequestHandler;
if (!redirect && getRequest().getHeader("Wicket-Ajax") == null)
{
lastRenderedPage = null;
}
// Add or replace any system provided header entry with the user provided.
if ((request != null) && (preHeader != null))
{
for (Map.Entry entry : preHeader.entrySet())
{
if (Strings.isEmpty(entry.getKey()) == false)
{
request.setHeader(entry.getKey(), entry.getValue());
}
}
// Reset the user provided headers
preHeader = null;
}
try
{
if (!redirect)
{
/*
* we do not reset the session during redirect processing because we want to
* preserve the state before the redirect, eg any error messages reported
*/
session.cleanupFeedbackMessages();
}
if (getLastResponse() != null)
{
// transfer cookies from previous response to this request, quirky but how old stuff
// worked...
for (Cookie cookie : getLastResponse().getCookies())
{
request.addCookie(cookie);
}
}
applyRequest();
requestCycle.scheduleRequestHandlerAfterCurrent(null);
if (!requestCycle.processRequestAndDetach())
{
return false;
}
recordRequestResponse();
setupNextRequestCycle();
if (followRedirects && lastResponse.isRedirect())
{
if (redirectCount++ >= 100)
{
fail("Possible infinite redirect detected. Bailing out.");
}
Url newUrl = Url.parse(lastResponse.getRedirectLocation(),
Charset.forName(request.getCharacterEncoding()));
if (isExternalRedirect(lastRequest.getUrl(), newUrl))
{
// we can't handle external redirects here
// just bail out here and let the user's test code
// check #assertRedirectUrl
return true;
}
if (newUrl.isAbsolute())
{
request.setUrl(newUrl);
final String protocol = newUrl.getProtocol();
if (protocol != null)
{
request.setScheme(protocol);
}
request.setSecure("https".equals(protocol));
if (newUrl.getHost() != null)
{
request.setServerName(newUrl.getHost());
}
if (newUrl.getPort() != null)
{
request.setServerPort(newUrl.getPort());
}
}
else
{
// append redirect URL to current URL (what browser would do)
Url mergedURL = new Url(lastRequest.getUrl().getSegments(),
newUrl.getQueryParameters());
mergedURL.concatSegments(newUrl.getSegments());
request.setUrl(mergedURL);
}
processRequest(null, null, true);
--redirectCount;
}
return true;
}
finally
{
redirectCount = 0;
}
}
/**
* Determine whether a given response contains a redirect leading to an external site (which
* cannot be replicated in WicketTester). This is done by comparing the previous request's
* hostname with the hostname given in the redirect.
*
* @param requestUrl
* request...
* @param newUrl
* ...and the redirect generated in its response
* @return true if there is a redirect and it is external, false otherwise
*/
private boolean isExternalRedirect(Url requestUrl, Url newUrl)
{
String originalHost = requestUrl.getHost();
String redirectHost = newUrl.getHost();
if (originalHost == redirectHost)
{
return false; // identical or both null
}
else if (redirectHost == null)
{
return false; // no new host
}
else
{
return !(redirectHost.equals(originalHost));
}
}
/**
* Allows to set Request header value any time. They'll be applied (add/modify) on process
* execution {@link #processRequest(MockHttpServletRequest, IRequestHandler, boolean)}. They are
* reset immediately after and thus are not re-used for a sequence of requests.
*
* Deletion (not replace) of pre-configured header value can be achieved by subclassing
* {@link #processRequest(MockHttpServletRequest, IRequestHandler, boolean)} and modifying the
* request header directly.
*
* @param key
* @param value
*/
public final void addRequestHeader(final String key, final String value)
{
Args.notEmpty(key, "key");
if (preHeader == null)
{
preHeader = Generics.newHashMap();
}
preHeader.put(key, value);
}
/**
*
*/
private void recordRequestResponse()
{
lastRequest = request;
lastResponse = response;
previousRequests.add(request);
previousResponses.add(response);
// transfer cookies from previous request to previous response, quirky but how old stuff
// worked...
if (lastRequest.getCookies() != null)
{
for (Cookie cookie : lastRequest.getCookies())
{
lastResponse.addCookie(cookie);
}
}
}
/**
* Renders the page specified by given {@link IPageProvider}. After render the page instance can
* be retrieved using {@link #getLastRenderedPage()} and the rendered document will be available
* in {@link #getLastResponse()}.
*
* Depending on {@link RenderStrategy} invoking this method can mean that a redirect will happen
* before the actual render.
*
* @param pageProvider
* @return last rendered page
*/
public Page startPage(final IPageProvider pageProvider)
{
// should be null for Pages
componentInPage = null;
// prepare request
request.setURL(request.getContextPath() + request.getServletPath() + "/");
IRequestHandler handler = new RenderPageRequestHandler(pageProvider);
// process request
processRequest(request, handler);
// The page rendered
return getLastRenderedPage();
}
/**
* Renders the page.
*
* @see #startPage(IPageProvider)
*
* @param page
* @return Page
*/
public Page startPage(final Page page)
{
return startPage(new PageProvider(page));
}
/**
* Simulates a request to a mounted {@link IResource}
*
* @param resource
* the resource to test
* @return the used {@link ResourceReference} for the simulation
*/
public ResourceReference startResource(final IResource resource)
{
return startResourceReference(new ResourceReference("testResourceReference")
{
private static final long serialVersionUID = 1L;
@Override
public IResource getResource()
{
return resource;
}
});
}
/**
* Simulates a request to a mounted {@link ResourceReference}
*
* @param reference
* the resource reference to test
* @return the tested resource reference
*/
public ResourceReference startResourceReference(final ResourceReference reference)
{
return startResourceReference(reference, null);
}
/**
* Simulates a request to a mounted {@link ResourceReference}
*
* @param reference
* the resource reference to test
* @param pageParameters
* the parameters passed to the resource reference
* @return the tested resource reference
*/
public ResourceReference startResourceReference(final ResourceReference reference,
final PageParameters pageParameters)
{
// prepare request
request.setURL(request.getContextPath() + request.getServletPath() + "/");
IRequestHandler handler = new ResourceReferenceRequestHandler(reference, pageParameters);
// execute request
processRequest(request, handler);
// the reference processed
return reference;
}
/**
* @return last response or null
> if no response has been produced yet.
*/
public MockHttpServletResponse getLastResponse()
{
return lastResponse;
}
/**
* The last response as a string when a page is tested via {@code startPage()} methods.
*
* In case the processed component was not a {@link Page} then the automatically created page
* markup gets removed. If you need the whole returned markup in this case use
* {@link #getLastResponse()}{@link MockHttpServletResponse#getDocument() .getDocument()}
*
*
* @return last response as String.
*/
public String getLastResponseAsString()
{
String response = lastResponse.getDocument();
// null, if a Page was rendered last
if (componentInPage == null)
{
return response;
}
// remove the markup for the auto-generated page. leave just component's markup
int end = response.lastIndexOf("