First use of ObMimic for out-of-container testing of Servlets and Struts (Part 2)

27 06 2007

Updated May 2013: ObMimic is now available from www.openbrace.com

As explained in part 1 of this posting, I’ve recently started trying out my newly-completed ObMimic library for out-of-container POJO-like testing of servlet code.

So, as promised, here are some of my early experiences from starting to use ObMimic for testing of some simple existing filters, listeners, an old “Struts 1” application (including out-of-container running of the Struts 1 controller servlet and configuration file), and tuckey.org’s “UrlRewriteFilter”.

These are just some initial “smoke test” experiments to check how ObMimic copes with simple situations, and to evaluate its usability. The ObMimic code itself has been tested in detail during its development, and its use for more complex and useful scenarios will be examined later.

Also note that this is primarily intended for my own historical records – actual ObMimic documentation, tutorials, Javadoc etc will be published when it’s ready for public release.

Experiment 1: Some simple listeners, filters and other basic Servlet API code

As a gentle first step, I revisited some listeners and filters in various old projects, and some other utility methods that take instances of Servlet API interfaces as arguments. Some of these had existing tests using mock objects, others didn’t. None of them do anything particularly complicated.

Writing out-of-container tests for them using ObMimic was straightforward and all worked as intended. As you’d expect, it basically just involves:

  • Creating and configuring the various objects needed as arguments. For example, using “new ServletContextMimic()” to create a ServletContext for use in a ServletContextEvent, and configuring it via the ServletContextState object returned by its getMimicState() method. Or creating and configuring an appropriate FilterChainMimic for passing to a filter.
  • Making the call to an instance of the class being tested.
  • Carrying out whatever checks are necessary to see if the code being tested has worked correctly.

The details, of course, depend on exactly what the code being tested is supposed to be doing.

For simple listeners, filters and other such code this is pretty straightforward. For simple unit-tests of such code, one could do much the same thing using mock objects, though from my (admittedly biased) point of view I think that the ObMimic code is slightly simpler and more direct than the same tests using mock objects, even for these simple cases. At any rate, it better fits my general preference for “state-based” rather than “interaction-based” testing, and as we’ll see later it can also handle far more complex situations.

For testing more complex code, there’d typically be more set-up involved to get the context, request, response etc configured as required (for example, so as to appropriately handle any “forwarding” done by the code being tested). Similarly, checking of results can become arbitrarily complicated depending on what the code being tested actually does. But that’s just the usual joy of testing something. We’ll see a more complex example later on.

Experiment 2: Struts 1 “Actions”

The next set of components examined were a handful of “Actions” in an old Struts 1 application. Actually, this was a bit of an anti-climax. The Struts “ActionForm” classes are “POJO”s anyway and already had test cases, and the “execute” method of each Struts “Action” class just needs:

  • A suitable HttpServletRequestMimic and HttpServletResponseMimic for use as the request and response (with appropriately-configured ServletContextMimic, HttpSessionMimic etc as necessary for each test).
  • An instance of the relevant ActionForm subclass, configured with the required property values.
  • A Struts ActionMapping instance, configured to map the relevant result strings to a suitable “ActionForward” instance.

The Struts ActionMapping and ActionForward classes are both suitably POJO-like, so are easily configured. There isn’t even any need to configure mapping of the ActionForward’s path to an actual target resource, as the “execute” method just returns the relevant ActionForward rather than actually carrying out the forwarding.

A few of the Action classes did need a fair bit of configuration of the HttpServletRequestMimic, its ServletContextMimic and the relevant HttpSessionMimic for some of the individual tests, but this was all relatively straightforward.

Although such tests check the Action’s “execute” method in isolation, it would also seem useful (and, for purposes of these experiments, more challenging) to be able to test the broader overall handling of a request. That is, including the mapping of a request to the correct ActionForm and Action and their combined operation. So the next experiment was to try and execute the Struts “controller” servlet, so as to be able to do “out-of-container” testing of the Struts configuration file, ActionForm and Action all together.

Experiment 3: Struts 1 controller servlet

The aim for this experiment was to try to get the Struts 1 controller servlet running “out-of-container” using ObMimic. This is partly motivated by wanting to be able to “integration test” the combination of a Struts configuration file, ActionForm and Action. But more importantly this seemed like a useful more general and more challenging test of what ObMimic can cope with, and an indication of how easy or hard it might be to get ObMimic working for other web frameworks.

The first step is to configure a ServletContext to be able to run Struts, in much the same way as one would configure a real web-application for Struts. Whilst there are several ways to do this, and the following example includes some things that aren’t stricly necessary, for purposes of this experiment I chose to do this as closely as possible to how it would be done in a web.xml. This resulted in code of the following form (adjusted a bit to help illustrate it):

// Create the ServletContextMimic and (for convenience) retrieve
// its MimicState and relevant objects within its MimicState.

ServletContextMimic servletContext = new ServletContextMimic();
ServletContextState contextState = servletContext.getMimicState();
WebAppConfig webAppConfig = contextState.getWebAppConfig();
WebAppResources webAppResources = contextState.getWebAppResources();

// Give the web-app a context path.

contextState.setContextPath("/examples");

// Add the struts-config file (provided as a system resource file
// in this class's package) as a static resource at the 
// appropriate location.

String strutsConfigResourceName
    = getClass().getPackage().getName().replace('.', '/') 
        + "/ExampleStrutsConfig.xml";
webAppResources.setResource("/WEB-INF/struts-config.xml",
    new SystemReadableResource(strutsConfigResourceName));

// Add a servlet definition for the struts controller, including 
// an init-parameter giving the location of the struts-config file.

InitParameters strutsControllerParameters = new InitParameters();
strutsControllerParameters.set("config", 
    "/WEB-INF/struts-config.xml");
int loadOnStartupOrder = 10;
ServletDefinition strutsController = new ServletDefinition(
    "strutsController", 
    ActionServlet.class.getCanonicalName(),
    strutsControllerParameters, 
    loadOnStartupOrder);
webAppConfig.getServletDefinitions().add(strutsController);

// Add a servlet mapping for the struts controller.

ServletMapping strutsControllerMapping 
    = new ServletMapping("strutsController", "*.do");
webAppConfig.getServletMappings().add(strutsControllerMapping);

// Initialize the context ("load-on-startup" servlets are 
// created and initialized etc).

ServletContextMimicManager contextManager 
    = new ServletContextMimicManager(servletContext);
contextManager.initializeContext();

Here, the SystemReadableResource class used to access the Struts config file is a ReadableResource as described in a previous article. It reads the content of the Struts config file to be used in the test, with this being supplied as a file in the same package as the above code. (Alternatively, the application’s existing Struts config file could accessed using a “FileReadableResource”, but the details would depend on the project’s directory structures, whereas the approach shown here also allows individual tests to use their own specific Struts configuration and keep it with the test-case code).

The rest of the classes involved are ObMimic classes. Hopefully the gist of this is fairly clear even without their full details.

One slight concession is that ObMimic doesn’t yet support JSP, so where the struts-config file specifies a path to a JSP file, the test needs to map such paths to a Servlet instead. This involves defining a suitable servlet (e.g. an HttpServlet subclass with a “doPost” method that sets the response’s status code to OK and writes some identifying text into the response’s body content, so that the test can check that the right servlet was reached). The corresponding servlet definition and servlet mapping can then be added to the above configuration of the ServletContextMimic (similar to those for the Struts controller servlet).

Then we just need a suitable request to process. Again, there are various ways to do this, and the particular details depend on the needs of the individual test. In outline, the code used for this experiment was along these lines (demonstrating a POST with body-content request parameters):

HttpServletRequestMimic request = new HttpServletRequestMimic();
request.getMimicState().setServletContext(servletContext);
request.getMimicState().setHttpMethodName("POST");
request.getMimicState().populateRelativeURIFromUnencodedURI(
    "/examples/exampleStrutsFormSubmit.do");
request.getMimicState().setContentTypeMimeType(
    "application/x-www-form-urlencoded");
try {
    request.getMimicState().setBodyContent("a=1&b=2", 
        "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
    fail("Attempt to configure request body content "
        + "for a POST failed due to unexpected " + e);
}

Here, the “populateRelativeURIFromUnencodedURI” method is one of various such short-cuts provided by ObMimic for setting request URI/URL details from various types of overall URL strings. This one takes a non-URL-encoded container-relative path, interprets it based on the ServletContext’s mappings etc, and populates the request’s context-path, servlet-path and path-info accordingly.

The response can start out as just a plain HttpServletResponseMimic with the correct ServletContext:

HttpServletResponseMimic response = new HttpServletResponseMimic();
response.getMimicState().setServletContext(servletContext);

So then we can invoke the Struts controller servlet, and it should all work just as it would within a servlet container, based on the supplied struts-config file and the ServletContextMimic’s configuration.

We could get hold of the Struts controller servlet from the ServletContextMimic by name, or maybe even just use a new instance of it. However, as we’ve gone to the effort of configuring a mapping for it, we might as well start with the request’s URI and do the actual look-up. For this I use a convenience method on ObMimic’s ServletContextMimicManager class that returns the target resource for a given context-relative path (again, there are various ways to do this, with or without any necessary filter chain etc, but this will do for these purposes):

ServletContextMimicManager contextManager 
    = new ServletContextMimicManager(servletContext());
Servlet actionServlet 
    = contextManager.getServletForRelativePath(
        "/exampleStrutsFormSubmit.do");
try {
    actionServlet.service(request, response);
} catch (IOException e) {
    fail(...suitable message...);
} catch (ServletException e) {
    fail(...suitable message...);
}

Then it’s just a matter of checking the response’s content (using, for example, calls such as “response.getMimicState().getBodyContentAsString()”), and anything else necessary to check that the request has been processed correctly.

Well, that’s the theory. So what happened in practice? A couple of minor problems were encountered, but easily overcome:

  • The version of Struts used appears to issue “removeAttribute” calls even where the attribute is not present. Although the Servlet API Javadoc for HttpSession specifies that its removeAttribute method does nothing if the attribute is not present, the Javadoc for ServletContext and ServletRequest don’t explicitly specify whether this is permitted or how it should be handled. ObMimic therefore treats such calls to ServletContext.removeAttribute and ServletRequest.removeAttribute as “API ambiguities”. Its default behaviour for these is to throw an exception to indicate a questionable call. But ObMimic’s handling of such ambiguous API calls is configurable, so the immediate work-around was just to have the test-case programmatically configure ObMimic to ignore this particular ambiguity for these particular methods, such that the removeAttribute calls do nothing if the attribute doesn’t exist. In retrospect it’s probably way too strict to treat this as an ambiguity – it’s a reasonable assumption that removeAttribute should succeed but do nothing if the attribute doesn’t exist, and there is probably lots of code that does this. So I’ve relented on this, and gone back and changed ObMimic so that this isn’t treated as an ambiguity anymore.
  • It turns out that the version of Struts used actually reads the contents of the /WEB-INF/web.xml file. This took a bit of hunting down, as the resulting exception wasn’t particularly explicit, but because the run is all “out-of-container” it was easy to step through test and into the Struts code in a debugger and find where it failed. The solution is to add a suitable web.xml file to the test class’s package and make this available to the ServletContext as a static resource at /WEB-INF/web.xml (in the same way as the struts-config.xml file). Actually, at least for this particular test, the precise content of the web.xml doesn’t seem to matter – Struts seems perfectly happy with a dummy web.xml file with a valid top-level <web-app> element but no content within it.

And that’s it. Having added a suitable /WEB-INF/web.xml static resource into the ServletContextMimic, Struts happily processes the request, pushes it through the right ActionForm and Action, and forwards it to the servlet that’s standing in for the target JSP. All within a “plain” JUnit test, with no servlet container involved (and easily repeatable with different struts-config.xml files, different context init-parameters, or with ObMimic simulating different Servlet API versions etc etc).

Experiment 4: URL Rewrite Filter

I’ve a few example/demo applications where I’ve played around with the UrlRewriteFilter library from tuckey.org to present “clean” URLs and hide technology-specific extensions such as “.jsp”. So I thought I’d try out-of-container testing of this as well.

The rules files that control the URL rewriting are fairly straightforward, but once you have multiple rules with wildcards etc it can become a bit fiddly to get exactly what you want. Tracking down anything that isn’t as intended can be a bit clumsy when it’s running in a servlet container, just from the nature of being in a deployed and running application. So I like the idea of being able to write and debug normal out-of-container test-cases for the config file, and using dummy or diagnostic servlets instead of the “normal” application resources.

This was pretty quick and straightforward after tackling the Struts controller servlet.

Although the details were very different, it again involves configuring a ServletContextMimic with the definitions, mappings and static resources for the UrlRewriteFilter and its configuration file. Much of the code was just copied and edited from the Struts experiment. Again, it proved useful to write a little servlet to which the “rewritten” URLs can be directed, with this having a “doGet” method that writes a message into the response’s body content, so as to indicate that it was invoked and what request URL it saw.

Then each actual test consists of using the relevant ObMimic facilities to obtain and invoke the filter chain for an example URL, with the filter chain’s ultimate target being a static resource whose content just shows if it was reached. After invoking the filter chain, the response’s body content can be examined to check which servlet processed it and what URL the servlet saw.

This wasn’t a very extensive test, as I just wanted to quickly see if it was basically possible, but it all worked without a hitch.

As with the preceding Struts experiment, the key issues are finding your way around the ObMimic classes in order to get the configuration you need, and figuring out what servlets and stuff you need in order to check the results.

Conclusions

So far, I’m happy with ObMimic technically. It’s particularly encouraging to have got both the Struts 1 controller servlet and the URL-rewrite filter running “out-of-container” so easily, as this suggests that it should be feasible to do the same for a variety of web frameworks and tools (especially once JSP support is implemented, which will be a priority for future versions of ObMimic).

On the other hand, I think the ObMimic Javadoc and other documentation needs more work. In practice, the key to using ObMimic is find your way around the MimicState classes that encapsulate each Mimic’s internal state. IDE code-completion is hugely useful for all this, as you can hunt around within each MimicState to look for the relevant properties and methods. However, it helps to have a rough idea of the general scheme of things – what’s available, what you’re looking for, and where things are most likely to be found. To a lesser extent it’s also helpful to know your way around the various supporting classes and methods that provide shortcuts for some of the more complex tasks. The documentation needs to provide some high-level help with all this.

Then there’s the Javadoc. This provides a comprehensive and detailed reference, but unfortunately it’s just too big and detailed. As it stands I think it would be too daunting for new users, or for casual use of the Javadoc. The first problem is that the standard Javadoc main index gives a full list of packages in alphabetical order. I’m hoping to deliver ObMimic as a single self-contained library, so there are a lot of packages, and the most useful routes into the Javadoc end up being scatered around the middle of a long list.

More generally, there are lots of specific tasks which are straightforward once you know how to do them, but hard to figure out from scratch. Things like how to make a static resource available within a ServletContext, or set up “POST” requests, or support JNDI look-ups, or maintain sessions across requests, or the easiest way to populate an HttpServletRequestMimic given the text of an HTTP request…

So my initial lessons from these experiments are:

  • ObMimic’s Javadoc needs to be made more approachable. One idea might be to supplement the standard Javadoc index page with a hand-written index page that groups the packages into meaningful categories and shows everything in a more sensible order.
  • It’d be useful to provide some kind of outline “map” of the MimicState classes, summarizing the properties and key methods of each class.
  • The ObMimic Javadoc needs to be supplemented by a set of task-oriented “how-to” guides.
Advertisements

Actions

Information

3 responses

3 06 2013
ObMimic Public Beta for Out-of-Container Servlet Testing | Closing Braces

[…] For some earlier posts that describe ObMimic and show some example code, see Experiments with out-of-container testing of Servlet code using ObMimic (Part 1) and First use of ObMimic for out-of-container testing of Servlets and Struts (Part 2). […]

24 07 2013
ObMimic 1.0 Beta 9 Released | Closing Braces

[…] First use of ObMimic for out-of-container testing of Servlets and Struts (Part 2) […]

25 08 2014
ObMimic 1.0 Released | Closing Braces

[…] First use of ObMimic for out-of-container testing of Servlets and Struts (Part 2) […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: