1  Introduction

Please note: This guide is currently based on testing of some relatively simple example JSF pages and the use of the “Mojarra” reference implementation of JSF 2.2, as provided by the initial release of Glassfish 4. Adjustments or additions to the ObMimic configuration and other code shown in this guide may be necessary if using other JSF implementations or versions, or for testing more complex JSF pages. However, the code shown here should at least provide a starting point for testing of JSF pages.

Also note that some of the ObMimic configuration settings described in this guide require the use of the “Professional” or “Enterprise” edition of ObMimic.

In general, the JSF framework can be run on top of ObMimic, and this allows out-of-container testing of JSF pages.

This includes being able to use all of ObMimic’s normal facilities when testing JSF pages (such as having comprehensive programmatic control over all relevant aspects of the ServletContext). With the use of a JSF “phase listener” it is also possible to access the JSF FacesContext during a test, allowing tests to use the JSF API to examine and manipulate JSF’s internal data structures during request processing.

This guide outlines an overall approach for testing a JSF page using ObMimic by running JSF’s FacesServlet. This includes showing how to set up and initialise JSF, and various ObMimic configuration settings that are necessary to successfully run the JSF FacesServlet.

This guide also outlines how to maintain session tracking and the JSF “view state” between requests, how to deal with CDI-injected resources, how tests can install JSF “phase listeners” to access the FacesContext and JSF API, and a number of other such issues that may arise when testing JSF pages.

2  Basic Approach

The overall approach for out-of-container testing of a JSF page using ObMimic is to initialise and invoke the JSF FacesServlet with the appropriate request details and with the relevant web-application contents and resources present within the ServletContext.

Typically this will involve:

  • Configuring ObMimic to accommodate any ambiguous Servlet API calls from the JSF implementation.
  • Configuring a ServletContext with any desired JSF settings and with the relevant JSF pages, configuration files and other such web-application content.
  • Initialising JSF.
  • Carrying out the desired tests by invoking the JSF FacesServlet, with appropriate configuration and inspection of the request, response and other Servlet API objects as usual. For JSF pages, this will often involve providing any necessary “injected” objects, and maintaining session-tracking and JSF “view states” across requests.
  • Where necessary, using JSF “phase listeners” to access the JSF FacesContext during tests, in order to examine (and if necessary manipulate) any relevant JSF data structures.

The various steps and issues involved are addressed one at a time in the “Step-By-Step Guide” below, including separate example code for each step together with accompanying explanations and notes.

However, the precise code needed for your tests and how best to organise such code will vary somewhat depending on issues such as:

  • Any ambiguous or implementation-dependent Servlet API calls made by the particular JSF version and implementation being used;
  • The types of JSF components and facilities used in the code being tested, and any additional requirements these may have;
  • The particular pages, resources and processing that are being tested (for example, what session-tracking mechanism you might wish to simulate, what JSF objects and state you might wish to configure or examine, and more generally what the test is intended to check);
  • What components and non-JSF services need to be “injected” into the application code being tested, which of these are going to be replaced by test-doubles of some kind for the purposes of your tests, and how this is to be done;
  • The testing framework being used, the organisation of your tests, and which aspects of the example code need to be done individually for each test and which can be done once for multiple tests (or can otherwise be provided by common subroutines, base classes etc).

The code examples shown in the following “Step-By-Step Guide” have therefore been limited to showing just the most basic and general configuration needed to test relatively simple JSF pages on the “Mojarra” reference implementation of JSF 2.2, and ignoring issues of how best to organise this code. You can then adjust, extend and organise the example code as necessary for your own tests.

3  Step-By-Step Guide

3.1  Required Libraries

In addition to the usual libraries needed to use ObMimic, tests of JSF pages will require a suitable JSF implementation to be present on the classpath when compiling and running the tests (that is, a “javax.faces.jar” library or equivalent).

Additional libraries may also be required if used by the JSF pages being tested or by other components used by them.

3.2  Servlet API Ambiguities

As of version 2.2.0, the Mojarra implementation of JSF 2.2 makes a number of Servlet API calls that ObMimic regards as API ambiguities.

For example, it appears to call the HttpServletResponse.setStatus(int) method for at least some error responses, but conversely it calls the deprecated and ambiguous HttpServletResponse.setStatus(int, String) method for at least some non-error responses. It also appears to issue a call to ServletContext.getResource(“/”) whose behaviour does not appear to be defined at all by the Servlet API.

ObMimic therefore needs to be configured to provide appropriate responses to these ambiguous calls instead of rejecting them. The following example code establishes the relevant ObMimic configuration:


  import com.openbrace.obmimic.config.ObMimic;
  import com.openbrace.obmimic.config.PropertyNames;
  import com.openbrace.obmimic.ambiguity.EmptyStringArgumentAmbiguity;
  import com.openbrace.obmimic.ambiguity.InvalidPartialRelativePathAmbiguity;
  import com.openbrace.obmimic.ambiguity.InvalidCompleteRelativePathAmbiguity;
  import com.openbrace.obmimic.ambiguity.InvalidArgumentValueAmbiguity;
  import com.openbrace.obmimic.ambiguity.ApiAmbiguityOptionEnum;
  import javax.servlet.ServletContext;
  import javax.servlet.http.HttpServletResponse;

  ...

  // Ignore API ambiguity for ServletContext.getInitParameter(String)
  // calls with an empty string as the parameter name.
  ObMimic.setRuntimeOverride(
      PropertyNames.constructApiAmbiguityPropertyName(
          ServletContext.class,
          "getInitParameter",
          new Class<?>[] { String.class },
          EmptyStringArgumentAmbiguity.class.getSimpleName(),
          null),
      ApiAmbiguityOptionEnum.IGNORE_AMBIGUITY.name());

  // Ignore API ambiguity for ServletContext.getResourcePaths(String)
  // calls with a path that is not a partial relative path.
  ObMimic.setRuntimeOverride(
      PropertyNames.constructApiAmbiguityPropertyName(
          ServletContext.class,
          "getResourcePaths",
          new Class<?>[] { String.class },
          InvalidPartialRelativePathAmbiguity.class.getSimpleName(),
          null),
      ApiAmbiguityOptionEnum.IGNORE_AMBIGUITY.name());

  // Ignore API ambiguity for ServletContext.getResource(String)
  // calls with a path that is "directory" path (such as "/").
  ObMimic.setRuntimeOverride(
      PropertyNames.constructApiAmbiguityPropertyName(
          ServletContext.class,
          "getResource",
          new Class<?>[] { String.class },
          InvalidCompleteRelativePathAmbiguity.class.getSimpleName(),
          null),
      ApiAmbiguityOptionEnum.IGNORE_AMBIGUITY.name());

  // Ignore API ambiguity for all HttpServletResponse.setStatus
  // calls with possibly-invalid argument values.
  ObMimic.setRuntimeOverride(
      PropertyNames.constructApiAmbiguityPropertyName(
          HttpServletResponse.class,
          "setStatus",
          null,
          InvalidArgumentValueAmbiguity.class.getSimpleName(),
          null),
      ApiAmbiguityOptionEnum.IGNORE_AMBIGUITY.name());

  ...

      

Note that:

  • Configuration of ObMimic’s API ambiguity handling requires the use of the “Professional” or “Enterprise” edition of ObMimic. A suitable ObMimic licence-key file is therefore necessary in order to test JSF pages when using a JSF implementation that issues Servlet API calls the ObMimic regards as ambiguous.
  • For details of the above API ambiguities and how they are handled as a result of the above configuration, refer to the ObMimic Javadoc for the relevant Servlet API method of the relevant Mimic.
  • For an explanation of the above code and alternative ways of setting these configuration options, refer to the accompanying “How To” guides How to set an ObMimic configuration property and How to configure handling of “API ambiguities”.
  • The above API ambiguities have been encountered when initialising JSF and using the JSF FacesServlet to test some simple JSF pages. Further such API ambiguities might occur within the JSF code when running your own particular tests or using a different JSF implementation, and if so ObMimic will need to be similarly configured to handle them appropriately.
  • The above ObMimic configuration should also be cleared/reset once finished with, for example by calling the ObMimic.clearAllRuntimeOverrides() method.
  • As these settings will be needed for any and all use of JSF, it will usually be appropriate to establish these configuration settings once for each set of JSF tests, and to then clear/reset them after the JSF tests. When using JUnit, for example, establishing and clearing this configuration might typically best be done within “BeforeClass” and “AfterClass” methods.

3.3  ServletContext Configuration

A ServletContext must be configured for use in the tests, with any necessary context parameters, resources, mime-type mappings, any desired container-dependent behaviour, and any other desired configuration and content. For example:


  import com.openbrace.obmimic.mimic.servlet.ServletContextMimic;
  import com.openbrace.obmimic.substate.servlet.WebAppConfig;
  import com.openbrace.obmimic.substate.servlet.WebAppResources;
  import com.openbrace.obmimic.substate.servlet.ServletContainerBehaviour;
  import com.openbrace.obmimic.support.servlet.PartialResourcePathBehaviourEnum;
  import com.openbrace.obmimic.config.ObMimic;
  import com.openbrace.obcommon.io.DirectoryNotFoundException;

  ...

  // Construct the ServletContext.
  ServletContextMimic context = new ServletContextMimic();

  // Set any necessary or desired JSF context parameters and any
  // other such simulated deployment-descriptor settings for the
  // application.
  WebAppConfig contextConfig = context.getMimicState().getWebAppConfig();
  contextConfig.getContextParams().set(
      "javax.faces.WEBAPP_RESOURCES_DIRECTORY",
      "/WEB-INF/resources");
  contextConfig.getContextParams().set(
      "javax.faces.STATE_SAVING_METHOD",
      "server");
  contextConfig.getMimeTypes().setMimeTypeForExtension("xhtml",
      "text/html;charset=UTF-8");

  // Make the web-application's JSF pages, faces-config.xml, and
  // all other relevant files and resources available to the
  // ServletContext at their appropriate context-relative paths.
  WebAppResources resources
      = context.getMimicState().getWebAppResources();
  String webappRootDir
      = ...file-system path of web-application root directory...;
  try {
      resources.loadResourcesFromDirectory(webAppRootDir);
  } catch (DirectoryNotFoundException e) {
      ... handle/report web-application directory not found ...
  }

  // Configure ObMimic to return null from any attempt to call
  // ServletContext.getResource with a "directory" path, as JSF
  // appears to call this with a path of "/" on its own (but
  // then seems quite happy if this returns null). Note that the
  // API ambiguity configuration already ignores this ambiguity,
  // but this configures how the call is actually processed now
  // that it is allowed.
  ServletContainerBehaviour behaviour
      = context.getMimicState().getContainer().getBehaviour();
  behaviour.setServletContextGetResourcePartialPathBehaviour(
      PartialResourcePathBehaviourEnum.TREAT_AS_NOT_FOUND);

  // Also (if desired) suppress the use of URL-rewriting for
  // session tracking (as per a web.xml entry of
  // <tracking-mode>COOKIE</tracking-mode>).
  behaviour.setSuppressSessionTrackingByUrlRewriting(true);

  ...

      

Note that:

  • JSF needs to be able to use the ServletContext to access the web-application’s JSF configuration files, the JSF page being tested, and other such web-application resources. The above example code shows this being done by loading the entire content of the web-application’s directory structure, given the file-system path of its root directory. Whilst this will often be the simplest approach, it is also possible to configure resources individually, and to supply the content of individual resources via in-memory byte arrays or via the classpath. For details of these alternatives, see “Alternatives and Options” below and the accompanying “How To” guide How to Simulate a Web-Application’s Static Resources.
  • This example code explicitly suppresses the use of URL rewriting for session tracking, so as to keep the URLs in requests and responses as simple as possible. Similarly, the example code shown below then assumes that cookies are being used for session tracking. See “Alternatives and Options” below for further explanation or if you want to use URL rewriting instead.
  • This example code also explicitly configures JSF to store “view states” on the server (within the user’s HttpSession) rather than on the client, and the example code shown below assumes this. For details of the alternative approach of storing the “view state” on the client, see “Alternatives and Options” below.
  • Depending on the nature of the tests, it may be appropriate to establish a single ServletContext for use throughout a series of tests, or to use a single ServletContext for a series of tests but with some adjustments to it for each test, or to use a separately-configured ServletContext for each individual test.

3.4  JSF Initialization

With the ServletContext appropriately configured, JSF and the JSF FacesServlet can then be initialized so as to be ready for use. Example code for this is as follows:


  import com.openbrace.obmimic.mimic.servlet.ServletContextMimic;
  import com.openbrace.obmimic.mimic.servlet.ServletConfigMimic;
  import com.sun.faces.config.ConfigureListener;
  import javax.faces.webapp.FacesServlet;
  import javax.servlet.ServletContextEvent;
  import javax.servlet.ServletException;

  ...

  // Initialise JSF via its context-initialization listener.
  ConfigureListener listener = new ConfigureListener();
  listener.contextInitialized(new ServletContextEvent(context));

  // Create and initialise the JSF servlet.
  FacesServlet jsfServlet = new FacesServlet();
  ServletConfigMimic servletConfig = new ServletConfigMimic();
  servletConfig.getMimicState().setServletContext(context);
  try {
      jsfServlet.init(servletConfig);
  } catch (ServletException e) {
      ... handle/report failure with ServletException ...
  }

  ...

      

Note that:

  • As with the ServletContext, depending on the nature of the tests it may be appropriate to initialise JSF once for a series of tests, or to shutdown and re-initialise the ServletContext and JSF between each test (for example, if different ServletContext settings or resources are needed for different tests).
  • Similarly, depending on your tests it may be most appropriate to use a single JSF FacesServlet for all tests or to use a separate JSF FacesServlet for each test (for example, if you are using a separate ServletContext for each test).

3.5  Testing a simple GET

With the ServletContext and JSF FacesServlet initialised, the JSF FacesServlet can then be used to process requests for JSF pages.

All of the normal ObMimic facilities are available to configure and examine the requests, responses, the ServletContext, any associated HttpSession etc, including any associated JSF-maintained objects and state stored within them.

The following example code shows the use of the JSF FacesServlet for a simple “GET” of an example JSF page as the first of a series of requests (that is, with no existing session to be continued).


  import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimicFactory;
  import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
  import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
  import com.openbrace.obmimic.mimic.servlet.http.HttpSessionMimic;
  import com.openbrace.obmimic.support.servlet.http.HttpRequestAndResponseMimicPair;
  import javax.servlet.http.Cookie;
  import javax.servlet.ServletException;
  import java.io.IOException;

  ...

  // Create the request and response for this test (with
  // the previously created and configured ServletContext,
  // as shown in previous example code).
  HttpRequestAndResponseMimicPair requestAndResponse
      = HttpServletRequestMimicFactory
            .createHttpRequestAndResponseMimicPair(context);
  HttpServletRequestMimic request = requestAndResponse.getRequest();
  HttpServletResponseMimic response = requestAndResponse.getResponse();

  // Configure the request.
  request.getMimicState().setHttpMethodName("GET");
  request.getMimicState().getRelativeURI().setServletPath("/faces");
  request.getMimicState().getRelativeURI().setPathInfo("/mypage.xhtml");
  request.getMimicState().getHeaders().add("Accept",
      "text/html,application/xhtml+xml,application/xml");
  ... any further set-up of request, context etc for this test ...

  // Invoke the JSF servlet.
  try {
      jsfServlet.service(request, response);
  } catch (ServletException e) {
      ... handle/report failure with ServletException ...
  } catch (IOException e) {
      ... handle/report failure with IOException ...
  }

  // Ensure that the response is committed (as the JSF servlet
  // doesn't appear to commit the response).
  if (!response.isCommitted()) {
      try {
          response.flushBuffer();
      } catch (IOException e) {
          ... handle/report failure to flush due to IOException ...
      }
  }

  // Retrieve various details from the response for subsequent
  // checking (and/or for use in any further requests).
  String pageContent
      = response.getMimicState().getBodyContentAsString();
  HttpSessionMimic session
      = request.getMimicState().getSessionDetails().getSession();
  Cookie sessionCookie
      = response.getMimicState().getCookies().getCookie("JSESSIONID");
  ... further retrieval and examination of response and other results ...

  ...

      

Note that:

  • For these examples, the JSF FacesServlet is called explicitly rather than via a RequestDispatcher, so its invocation does not depend on the application having a suitable servlet mapping for the JSF FacesServlet. However, JSF uses the request’s “servletPath” and “pathInfo” components to identify the page that is being requested through the JSF FacesServlet, and also when constructing further URLs for JSF pages (and possibly for other such purposes). The example code above is based on treating the JSF FacesServlet as having a servlet mapping of /faces/*. The request’s relative URI is therefore populated with a “servletPath” of /faces and a “pathInfo” that excludes that prefix and consists of the required page’s actual relative path within the application. The values used will need to be adjusted as appropriate if you wish to simulate a different servlet mapping for the FacesContext.
  • The example code above shows how the resulting HttpSession and corresponding session cookie can be retrieved from the response (in particular, if the cookie needs to be used in further requests to continue the same session, as shown in the subsequent example code that follows).
  • Where the request needs to be for an existing session with existing JSF “view state” and other such content, a suitable HttpSession will need to be configured and its session ID supplied by the request. Whilst it is possible to explicitly populate an HttpSessionMimic with the necessary content, this is potentially non-trivial and requires an understanding of the data structure used to hold the JSF “view state”, and how ObMimic handles session details. In practice, the easiest and most general way to establish the necessary session and “view state” is by processing the relevant sequence of preceding requests within the session. For an example of how to do this, see the code example below for testing a POST (which uses a GET of a form in order to establish the necessary session state for then testing a POST from that form).

3.6  Retrieving the JSF ViewState ID

When saving state at the server (rather than on the client), JSF forms use a hidden field named “javax.faces.ViewState” whose value identifies the JSF “view state” being used by the form. When submitting a POST from the form, this needs to be included in the submitted data in order for JSF to be able to locate and retrieve the form’s existing state from within the relevant HttpSession.

The view state identifier will also be necessary if you need to examine the actual contents of the view state itself (for example, to retrieve and examine any “beans”s or other such JSF-maintained objects stored in the form’s view state).

In general, the simplest and safest way to retrieve a form’s view state identifier is to GET the form and then locate and extract the “javax.faces.ViewState” field’s value directly from the response’s body content (via whatever code or tools you are using to analyse the response’s body content).

Note that although the view state contents and their identifiers are stored within an HttpSession attribute, and the identifiers can potentially be determined by examining the relevant data structure within the HttpSession attribute, this is not recommended because the data structures used are non-trivial and could potentially vary between JSF implementations and versions. In any case, the identifiers are generated by JSF and the data structures used can “evict” obsolete view states, so determining a view state’s identifier by examining the HttpSession content is only possible if you can successfully identify the correct entries without using the identifiers to find them. (In practice this is often possible for test cases, as there is usually only a single form and a known sequence of uses of that form, making it possible to predict the order in which the view states will appear within the data structure. Nevertheless, obtaining a view state identifier from the relevant form’s actual content is likely to be simpler and safer than attempting to locate it within the HttpSession based on assumptions about the JSF data structures involved).

3.7  Testing a POST

As explained above, to test a POST submission of a form the most convenient way to establish the necessary session, JSF “view state” and other such set-up will usually be to first GET the form. The POST can then be carried out as part of the same session, using the session ID and “view state” identifier returned by the GET.

Submitting the POST is otherwise as per any other POST, as described in the accompanying “How To” guide How to simulate an HTTP “POST” form submission.

Example code for this is as follows:


  import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimicFactory;
  import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
  import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
  import com.openbrace.obmimic.substate.servlet.RequestParameters;
  import com.openbrace.obmimic.support.servlet.http.HttpRequestAndResponseMimicPair;
  import javax.servlet.http.Cookie;
  import javax.servlet.ServletException;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;

  ...

  // GET the form's page and retrieve relevant details from the
  // response, as per previous examples and explanations.
  ... GET the page ....
  Cookie sessionIdCookie = ... // As per previous example.
  String jsfViewStateId = ... // As explained in previous section.

  // Create the request and response for the POST (with
  // the previously created and configured ServletContext,
  // as shown in previous example code).
  HttpRequestAndResponseMimicPair requestAndResponse
      = HttpServletRequestMimicFactory
            .createHttpRequestAndResponseMimicPair(context);
  HttpServletRequestMimic request = requestAndResponse.getRequest();
  HttpServletResponseMimic response = requestAndResponse.getResponse();

  // Set the POST's URL, headers, cookies etc, including the session
  // cookie from the preceding GET of the form.
  request.getMimicState().setHttpMethodName("POST");
  request.getMimicState().getRelativeURI().setServletPath("/faces");
  request.getMimicState().getRelativeURI().setPathInfo("/mypage.xhtml");
  request.getMimicState().getCookies().add(sessionIdCookie);

  // Set up the POST's content, including the JSF "view state"
  // identifier from the preceding GET of the form.
  RequestParameters postParameters = new RequestParameters();
  postParameters.set("...field name...", "...field value...");
  postParameters.set("...field name...", "...field value...");
  ... etc ...
  postParameters.set("...submit button name...", "Submit");
  postParameters.set("javax.faces.ViewState", jsfViewStateId);
  try {
      request.getMimicState().setWWWFormUrlEncodedBodyContent(
          postParameters, "UTF-8");
  } catch (UnsupportedEncodingException e) {
      ... handle/report unsupported encoding (shouldn't ever occur) ...
  }

  // Invoke the JSF servlet (and ensure the response is committed,
  // as the JSF servlet doesn't appear to commit the response).
  try {
      jsfServlet.service(request, response);
      if (!response.isCommitted()) {
          response.flushBuffer();
      }
  } catch (ServletException e) {
      ... handle/report failure with ServletException ...
  } catch (IOException e) {
      ... handle/report failure with IOException ...
  }

  // Examine the response and other results as usual
  ...

      

Note that:

  • The session is continued by taking the session Cookie returned by the GET and including it in the request. ObMimic’s normal processing then automatically recognises this, and populates the request to use the corresponding existing HttpSession.
  • The input fields will obviously vary for each form, and are therefore only shown in general terms in the above example code. Care may be needed with any field names that are generated automatically by JSF.

3.8  Injected Objects and Services

In practice, any JSF page that you wish to test will in turn use objects that would normally be automatically constructed and injected into the relevant Servlet API or JSF scope by the container. These in turn will usually depend on further objects and services installed into them by the container's dependency injection facilities, or possibly looked-up using JNDI.

Depending on what you are trying to test, you will typically want to construct and use some mix of real and test-double objects. For example, you may wish to use the appropriate real implementations for any plain-Java “bean” objects used in your JSF code, but supply any EJB objects that would normally be “injected” into those objects by manually populating the relevant fields with a suitable test-double of some kind.

In general this is outside the scope of ObMimic and can be done in any way you wish, but it may be worth noting that:

  • You can install objects into the relevant Servlet API scope (and hence the corresponding JSF scope) via the appropriate Servlet API mimic as usual (for example, by setting request attributes on the HttpServletRequestMimic).
  • You may be able to use a suitable dependency-injection framework to carry out some or all of the necessary instantiation and injection of the appropriate objects for your tests, subject to this being able to handle the particular annotations used in your code, and being runnable “out-of-container’, and not having excessive overheads or complexity. (However, note that as of the time of writing we have not yet found an appropriate way to use the reference implementation of CDI for this).
  • Other specialist tools for out-of-container testing and for simulating various APIs and services may be of some help with this (for example, for running some types of EJB outside of a container, or for simulating database accesses for testing purposes, or for simulating the JMS API etc).
  • Where a JNDI look-up is used, ObMimic’s built-in JNDI simulation can be configured to return a suitable object for use in the test. For details, see the accompanying “How To” guide How to cater for JNDI look-ups.

3.9  Accessing JSF Data Structures

If you wish to examine or manipulate internal JSF data structures during a test, you will typically need access to the FacesContext object. However, a separate FacesContext is used by each request, and is created and “released” (after which it is no longer useable) within the JSF FacesServlet’s internal processing.

The recommended approach for examining or manipulating the FacesContext within a test is therefore to define and install a suitable JSF “phase listener”, so that its event handlers are notified of the start and end of each phase of JSF processing and can access the FacesContext as necessary.

Example code for installing such a listener (assuming a class name of MyExamplePhaseListener for the test’s PhaseListener implementation) is as follows:


  import javax.faces.event.PhaseListener;
  import javax.faces.lifecycle.LifecycleFactory;
  import javax.faces.lifecycle.Lifecycle;

  ...

  // Create an instance of the required listener.
  PhaseListener listener = new MyExamplePhaseListener();

  // Install the listener globally for all JSF requests.
  LifecycleFactory lifecycleFactory
      = (LifecycleFactory) FactoryFinder.getFactory(
          FactoryFinder.LIFECYCLE_FACTORY);
  Lifecycle lifecycle
      = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
  lifecycle.addPhaseListener(listener);

  ... process the request(s) for which the listener is to be used ...

  // Remove the listener.
  lifecycle.removePhaseListener(listener);

  ...

      

Note that:

  • The above example code assumes that JSF has already been successfully configured and initialised.
  • Within the listener’s event-handling methods, the FacesContext is available via the getFacesContext() method of the PhaseEvent passed to the event-handler.
  • The above example code installs the listener into JSF globally for all requests, and then uninstalls it once the relevant request or requests have been processed. It is also possible to install request-specific listeners, but this is more complex (and would typically require the use of a global listener to install the request-specific listener anyway). For most testing purposes it will usually be appropriate to use globally-installed listeners but install and uninstall them as necessary — at worst, such listeners can always ignore events that are not relevant to them.
  • Phase listeners can also be configured in the application’s faces-config.xml, but for test cases it’s probably more convenient and flexible to install and remove such listeners programmatically as and when required.

It is also potentially possible to directly examine or manipulate a form’s JSF “view state” when setting up a test or examining its results. For the “Mojarra” reference implementation of JSF 2.2, and when configured to store state on the server, an attribute named “com.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMap” is used within the relevant HttpSession to hold the view states for each session. The data structure used holds the view state of each currently-stored occurrence (“physical view”) of each form (“logical view”), but with limits on the number of these and eviction of “old” view states when this number is exceeded. However, direct access to the view state contents within this attribute is not recommended, as it requires an understanding of the data structures used, which may differ between implementations and may be subject to change.

4  Alternatives and Options

4.1  Session Tracking by URL Rewriting

By default, JSF uses both a cookie and URL rewriting to send a session ID to the client in its first response, and thereafter uses the cookie if this is returned from the client or URL rewriting if the client does not return the cookie.

The example code above uses cookies for all session tracking, and explicitly suppresses the use of session tracking by URL rewriting in order to keep all URLs as simple as possible.

If you instead wish to use URL rewriting for session tracking, you can do this by:

  • Not issuing the setSuppressSessionTrackingByUrlRewriting(true) call to the ServletContextMimic’s mimicState.container.behaviour (or passing it false instead of true);
  • Optionally calling setSuppressSessionTrackingByCookies(true) on the ServletContextMimic’s mimicState.container.behaviour to suppress the use of cookies for session tracking (if you want to use just URL rewriting on its own);
  • Determining the session ID from the session’s first response by extracting it from an appropriate URL within the response content (typically, by processing a GET for a form and then extracting the session ID from the resulting form’s “action” URL). The session ID will be present in such URLs as the value of a “jsessionid” path parameter at the end of the URL’s last path segment (that is, in a ;jsessionid=... session id ... string at the end of the URL’s path but preceding any “?’-prefixed query string);
  • Specifying the session ID in any and all further requests that need to be part of the same session, by passing the session ID string to the setSessionIdRequestedByURL method of the HttpServletRequestMimic’s mimicState.sessionDetails (for example, using a statement such as request.getMimicState().getSessionDetails().setSessionIdRequestedByURL(sessionId);). Note, however, that the session ID should not be explicitly added into the HttpServletRequestMimic’s URL, as ObMimic does not currently provide any explicit support for such “path parameters”, and including it within the request’s pathInfo currently appears to cause problems for the JSF implementation.

4.2  Populating the ServletContext Resources

The example code given above populates the ServletContext’s resources with the content of a directory structure, which is assumed to contain all of the web-application’s relevant files (including its JSF configuration files and JSF page sources and other such resources) at the appropriate relative paths. In order to do this the appropriate directory structure needs to present at a known location.

Depending on your build tools and process, you may already have a directory that contains the necessary web-application contents with the appropriate directory structure, or you might need to adjust your build to provide this. Note that it is not generally necessary for this directory structure to contain the entire contents of the web-application — all that matters is that any resources that JSF accesses via context-relative paths are present and at the correct relative locations within the ServletContext. For example, it will not typically be necessary for the web-application’s “/lib” directory to be present and fully populated.

In general it will be impractical and undesirable to directly hard-code the path for this directory into your tests. Some configurable means of supplying it to your tests is therefore desirable (or to at least supply some suitable root path from which the full path can be constructed). Depending on your testing framework, build processes and run-time environment, you may already have some way of providing such run-time settings to your tests, or your tests may already have some location that they can use as a starting point from which the necessary path can be derived. If all else fails, an environment variable or JVM system property could be used to supply the necessary path or root location.

As an alternative (or in addition to this), if only a small number of files are actually necessary for a test, or if test-specific versions of any of the files are needed, it is also possible to load each relevant file into the ServletContext individually. Similarly, it is possible to load the contents of a directory structure and then override/replace individual files within it. Also note that in addition to using actual files, the ObMimic facilities for this also support the provision of the necessary content via in-memory byte arrays or via the classpath.

Yet another alternative is to have your test code explicitly construct and populate a temporary directory with the necessary content, and then use that directory structure to provide the ServletContext’s resources.

For further details of how to configure the ServletContext’s resources, see the accompanying “How To” guide How to Simulate a Web-Application’s Static Resources.

4.3  Client-Side JSF View State Storage

All of the example code shown above assumes that JSF is configured to save state on the server. In this mode, JSF uses a hidden “javax.faces.ViewState” field within each form whose value is the identity of a “view state” whose actual content is maintained within the relevant HttpSession.

However, JSF also supports the option to save such state on the client instead. This uses the “javax.faces.ViewState” field to hold an encoding of the view state’s actual content rather than just an identifier.

This can be controlled by means of a context parameter named “javax.faces.STATE_SAVING_METHOD”. JSF saves view states on the server if this context parameter is absent or has a value of “server”, or on the client if this context parameter is present and has a value of “client”.

If you want JSF to save the view state data on the client rather than the server, you will therefore need to:

  • Configure the ServletContext’s “javax.faces.STATE_SAVING_METHOD” context parameter with a value of “client” (that is, as shown in the example code above but with a value of “client” rather than “server”).
  • Locate and extract the value of the form’s “javax.faces.ViewState” field each time the form is returned in a response, and submit the latest value of this field whenever submitting data from the form. Note that this is essentially the same as when saving state on the server, except that the value of the field is longer and will change from request to request (as it is a string containing an encoded representation of the current actual view state data, rather than just containing an identifer for that data).

5  Explanation and Further Details

In general, application code that uses JSF will primarily make use of plain-Java components that can generally be unit-tested as per normal Java code (together with the use of normal mocking tools or other test doubles for any injected objects or services needed by the code).

Conversely, overall integration and system testing of complete JSF pages can often be carried out adequately via high-level in-container testing (using either an embedded container or by testing in an external Java EE application server by means of normal HTTP requests to the application server).

However, for more detailed testing of individual JSF pages it can still be useful to use ObMimic for out-of-container testing of complete JSF pages. In particular, such ObMimic-based tests:

  • Allow full programmatic control over the Servlet API (in particular, full control over the ServletContext and the ability to programmatically adjust the ServletContext for each test);
  • Make it possible for tests to programatically install test-specific “phase listeners” for access to JSF data structures during the test;
  • Provide additional instrumentation of the Servlet API calls made during tests (in the form of ObMimic’s “mimicHistory” and “mimicListeners” facilities, and detection of any “ambiguous” Servlet API calls);
  • Are free of the inherent overheads, restrictions and complexity of in-container testing.

The main mechanism for using ObMimic for out-of-container testing of JSF pages is to run the JSF FacesServlet on top of ObMimic. This servlet then handles all of the JSF processing, including locating and processing the requested JSF page, maintaining JSF’s internal data structures as necessary, rendering of page content into the response, and any necessary use of the Servlet API to process EL expressions.

In principle this is the same as using ObMimic for any other servlet. The main issues specific to running the JSF FacesServlet are that:

  • The ServletContext must be suitably configured to support JSF (for example, to provide resources such as the JSF page source, a suitable faces-config.xml file, and other such files at the appropriate location within the ServletContext).
  • ObMimic must be configured to appropriately handle any ambiguous Servlet API calls issued by the JSF FacesServlet or by any other JSF code invoked during the tests.
  • There is a JSF initialisation process that is normally carried out as part of the ServletContext initialisation, and this initialisation must be completed prior to any invocation of the JSF FacesServlet.

Beyond that, testing a JSF page by running the JSF FacesServlet is in essentially the same as testing any other servlet, and just depends on what Servlet API calls are made by the FacesServlet when processing the JSF page, and the particular requirements for the request, response, ServletContext, HttpSession etc for each test.

However, the approach and example code outlined in this guide have only been tested on some simple example JSF pages on the Mojarra implementation of JSF 2.2, as provided by the initial release of Glassfish 4. In principle the same overall approach should also work for more complex JSF pages and for other JSF implementations (subject to being likely to need appropriate adjustments to the example code and additional configuration), but this has not been exhaustively examined. Please let us know if you encounter any problems with this (via the relevant ObMimic discussion forum or via any of the other support mechanisms described on the Support.html page or our website’s Contact Us page).

Note that:

  • This guide’s “Step-by-Step Guide” and example code show the individual steps necessary to test a JSP page by running the FacesServlet on top of ObMimic. However, they make no attempt to show how the various steps involved should be organised within your tests, as this will depend on how the tests themselves are organised, the test framework being used, and the commonality and differences between the tests etc.
  • Whilst there is some degree of overhead involved in JSF’s initialisation process, in practice this does not appear excessive (and is far less than that involved in initialisation of a complete Java EE server and application). In addition, depending on the particular tests involved and what types of changes to the ServletContext and its resources might be necessary for each test, it can often be appropriate to carry out this initialisation just once for a set of tests during their overall set up, instead of repeating it for each individual test.