Skip to main content

Container-free Testing with Mockrunner

September 13, 2005

{cs.r.title}








Contents
What is a Mock Object?
What is Mockrunner?
Core Mockrunner Classes
Testing Struts
   Extending Mockrunner
Testing JDBC
Conclusion
Resources

Unit testing is an essential practice for anyone seeking to
develop better-designed, higher quality software. Testing the
individual objects that make up your system provides you with a
much higher degree of confidence in both the design and general
quality of the application. However, testing objects in isolation
often presents some unique challenges as few useful objects operate
independently of others. The challenges are even greater in the
context of a J2EE application where the container manages many of
the collaborating objects. This article will look at the use of
mock objects and the Mockrunner testing
framework as a means of overcoming many of these challenges.

What is a Mock Object?

The topic of mock objects is often a point of confusion for
those new to test-driven development, so before diving into the
details of Mockrunner, it would be best to make sure you have an
understanding of what exactly is a mock object.

A mock object, also referred to simply as a mock,
plays the role of a stand-in for a real application object in the
context of a unit test. A key objective in unit testing is to
ensure you are testing only the functionality of your class under
test and not that of its collaborating objects. Mocks help you to
achieve this goal by providing a replacement object, which
implements the same interface, but contains little or no state or
behavior. This allows you to focus solely on the logic of the class
you are testing without concern for the impacts of the objects with
which it interacts. I'll refer you to the resources section for links to further insights
and perspectives on the topic.

What is Mockrunner?

Mockrunner is a lightweight testing framework, built on JUnit, for testing J2EE applications.
Its focus is on transparently simulating your application's runtime
environment so you can easily create unit tests that run
out-of-container and independently of deployment descriptors or
other external artifacts.

The core distribution provides built-in support for testing the
most commons J2EE component types including the Servlet APIs, JDBC,
JMS, EJB, as well as Struts Actions. Its comprehensive API support
makes Mockrunner a compelling tool as it provides a consistent
framework for testing your applications from end to end.

Core Mockrunner Classes

There are three primary categories of classes found in the
Mockrunner framework: Test Modules, TestCase Adapters, and Mock
Object Factories. These categories do not necessarily denote a
particular object hierarchy, but rather represent a conceptual
grouping. The following table describes the purpose of each
category and lists some examples of the corresponding classes found
in the distribution.

Type Purpose Examples
Test Modules Provides the central runtime behavior of the framework.
ActionTestModule
EJBTestModule
HTMLOutputModule
JDBCTestModule
ServletTestModule
TagTestModule
TestCase Adapters Extensions of JUnit's TestCase that
act as wrappers around the functionality of their underlying test
modules.
ActionTestCaseAdapter
BasicJDBCTestCaseAdapter
JMSTestCaseAdapter
BasicTagTestCaseAdapter
ServletTestCaseAdapter
Mock Object Factories Factory objects for creating the various types of
mocks required by a particular J2EE component type.
ActionMockObjectFactory
EJBMockObjectFactory
JDBCMockObjectFactory
JMSMockObjectFactory
WebMockObjectFactory

The diagram in Figure 1 shows a high-level view of how the
classes in the various categories relate.

Core Mockrunner Classes
Figure 1. Core MockRunner Relationships

At the heart of the framework are the various test modules. The
test modules provide the runtime testing behavior and are used to
mimic the functionality of the J2EE container. Although you can
interact directly with the test modules, which may be necessary if
you already have a standard base class from which your tests
extend, it is often more convenient to have your test cases extend
from one the framework's TestCase adapters.

The TestCase adapters are extensions of JUnit's
TestCase. They provide a standard implementation of
the Adapter pattern and are used to wrap the functionality of the
underlying test modules and related mock object factories. Under
each technology type you will find two different versions of the
adapters:

  1. Basic<Technology>TestCaseAdapter contains a reference to the technology-specific test module and
    related mock object factory. Examples in the distribution include BasicServletTestCaseAdapter and
    BasicEJBTestCaseAdapter.

  2. <Technology>TestCaseAdapter contains
    a reference to all of the technology test modules and mock object
    factories defined in the Mockrunner distribution. Examples include
    the ActionTestCaseAdapter and
    JMSTestCaseAdapter.

You may find the non-basic versions to be useful in situations
where you are testing a class that mixes multiple J2EE
technologies. For instance, if you are testing a servlet class that
directly looks up and interacts with an EJB, the non-basic version
may be useful to you. Assuming your application has an adequate
level of abstraction, you will likely find the basic versions more
generally applicable and will be the ones on which I will focus in
this article.

Let's begin by looking at some specific examples.

Testing Struts

The Struts framework is
often the key technology used in an application's web tier. Given
its role in an application it is important for you to provide a
sufficient level of test coverage to ensure the quality of this
tier. Unfortunately, testing Struts Actions can be difficult due to
their dependence on objects managed by the web container and the
Struts infrastructure itself. Luckily, Mockrunner provides you with
the tools to easily create tests that run out-of-container and
independently of any external configuration. Let's begin by looking
at a simple Action class used to search an online
catalog.

public class SearchAction extends Action {

public ActionForward execute(ActionMapping mapping,
                     ActionForm actionForm,
                     HttpServletRequest request,
                     HttpServletResponse response)
                     throws Exception {

  SearchForm form = (SearchForm) actionForm;
  String query = form.getQuery();

  SearchService service = getSearchService();
  List list = service.searchCatalog(query);

  if (list != null && !list.isEmpty()) {
     request.setAttribute("results", list);

  } else {
    ActionMessages messages = new ActionMessages();
    messages.add(ActionMessages.GLOBAL_MESSAGE,
              new ActionMessage("msg.no.results"));
    saveMessages(request, messages);
    return mapping.getInputForward();
  }
  return mapping.findForward(ForwardKeys.SUCCESS);
}

SearchService getSearchService() {
  ServletContext context =
    getServlet().getServletContext();
  return (SearchService)
    context.getAttribute("searchService");
}
}

The SearchAction retrieves the user input from a
subclass of ActionForm to determine the user's query.
It then invokes the business logic tier's
searchCatalog() method and depending on the results
returned from the service, the action will either populate the
request with the list of results or an ActionMessage
indicating no results were found.

You'll begin by creating a subclass of
BasicActionTestCaseAdapter. This class provides a
wrapper around the ActionTestModule and
ActionMockObjectFactory, which provide the
functionality required for testing Struts Actions. The
SearchAction interacts with a business logic tier
object bound the ServletContext, so you'll first need
to configure a mock version of this class and bind it to the
framework's MockServletContext. This example will use
EasyMock to create a mock
version the SearchService. Please refer back to Lu
Jian's Mock Objects in Unit Tests for the details of its syntax. The
setUp() method for this test case is defined as
follows:

public class SearchActionTest 
  extends BasicActionTestCaseAdapter {

  private MockControl ctrl;
  private SearchService service;
  private final String query = "Test Query";
   
  protected void setUp() throws Exception {
    super.setUp();
    // Configure EasyMock-managed mock service
    ctrl =
      MockControl.createControl(
        SearchService.class);
    service = (SearchService) ctrl.getMock();
       
    ActionMockObjectFactory factory =
        getActionMockObjectFactory();
    MockServletContext context =
        factory.getMockServletContext();
    // bind mock service to the servlet context
    context.setAttribute(
        ServiceKeys.SEARCH_SERVICE, service);
  }

  ...
}

It is important to note if you override an adapter's
setUp() or tearDown() method that you
always invoke the super class version to ensure proper
initialization or clean up of the test case.

With the setUp() configuration complete you can
proceed to test the action's execute() method. In this
example, you can assume the user has input a valid search string,
which leaves two primary test scenarios:

  1. A valid list of results was returned and bound to the request and the request was forwarded to the results page.

  2. No results were found and the user was returned to the input page with an ActionMessage bound to the request indicating the status of the query.

Let's begin with the first scenario.

public void testExecute() {
  List expected = new ArrayList();
  expected.add("foo");
   
  // configure expected method call
  // and return value
  service.searchCatalog(query)
  ctrl.setReturnValue(expected);
  ctrl.replay();
   
  // run the action's execute method
  SearchForm form = createForm(query);
  actionPerform(SearchAction.class, form);
 
  // verify the results of the action processing
  verifyNoActionErrors();
  verifyNoActionMessages();
  verifyForward(ForwardKeys.SUCCESS);
 
  List results =
    (List) getRequestAttribute("results");
  assertEquals(expected, results);
 
  ctrl.verify();
}

private SearchForm createForm(String query) {
  SearchForm form = new SearchForm();
  form.setQuery(query);
  return form;
}

The mock SearchService is configured to expect its
searchCatalog() method be invoked and return a
List containing one result. For the purposes of the
test, the actual contents of the list are irrelevant. Next, the
actionPerform() method is called which sets the
runtime into motion and invokes the action's execute()
method. After the execute() method has been run, you
can use the various verifyXXX() methods
provided by BaseActionTestCaseAdapter to verify the
test results. Specifically, a check is made to verify no
ActionErrors or ActionMessages were
created and that the expected forward occurred. Finally, a quick
examination of the HttpServletRequest is performed to
ensure it was populated with the results of the query.

Mockrunner requires no external configuration, so the first test
method is complete and ready to be run. You can use any standard
approach to executing this test including using JUnit's TestRunner,
Ant, or your IDE's built-in JUnit support.

With the first test complete, it's time to move on to testing
the second scenario.

public void testExecuteNoResults() {
   ctrl.expectAndReturn(service.searchCatalog(query),
                        Collections.EMPTY_LIST);
   ctrl.replay();

   getMockActionMapping().setInput("/testInput");
   actionPerform(SearchAction.class, createForm(query));
   verifyNoActionErrors();
   verifyActionMessagePresent(MsgKeys.MSG_NO_RESULTS);
   verifyForward(getMockActionMapping().getInput());

   ctrl.verify();       
}

As in the previous test example, the first step is to configure
the expectations of the SearchService. This time it
needs to be configured to return an empty list indicating no
results were found. The next step is to configure the action's
input forward by using the setInput() method of the
MockActionMapping class. Once again, a call to invoke
the actionPerform() method is made to run the action's
execute() method. This time a verification check is
made to ensure an ActionMessage was bound to the
request and the user was redirected to the input page.

You now have a complete out-of-container test case for the
SearchAction.

Extending Mockrunner

Many of the applications I've developed over the past couple of
years have used a combination of Struts and the Spring Framework. A common
approach when using these two technologies together is to have your
Actions lookup bean references from the Spring
WebApplicationContext. Obtaining a reference to this
class can be done either by subclassing one of Spring's Action
extensions or by using its WebApplicationContextUtils
class. The core Mockrunner distribution does not provide support
for Spring, however, this capability can be easily added by writing
a simple extension to the framework.

The first thing you need to create is a mock implementation of
Spring's WebApplicationContext. This will allow you to
test your actions without the need to load the Spring container or
rely on bean definitions wired in Spring's
applicationContext.xml. Since
WebApplicationContext is an interface, it is easy to
create a stubbed version of this class and add the minimal amount
of functionality needed to run a test. Let's look at an excerpt
from this class showing the key methods.

public class MockWebApplicationContext 
                implements WebApplicationContext {

   private long startup;
   private ServletContext servletContext;
   private Map beanMap =
      Collections.synchronizedMap(new HashMap());

   public MockWebApplicationContext(ServletContext
                                    servletContext) {
      this.servletContext = servletContext;
      startup = Calendar.getInstance().getTimeInMillis();
   }

   public ServletContext getServletContext() {
      return servletContext;
   }

   public void addBean(String beanName, Object bean) {
      beanMap.put(beanName, bean);
   }

   public Object removeBean(String beanName) {
      return beanMap.remove(beanName);
   }

   public Object getBean(String beanName)
                       throws BeansException {
      return beanMap.get(beanName);
   }

   ...
  
}

With the MockWebApplicationContext complete, the
next step is to create a simple extension of the
BasicActionTestCaseAdapter to incorporate this
mock.

public abstract class 
  BasicSpringActionTestCaseAdapter
    extends BasicActionTestCaseAdapter {

  private MockWebApplicationContext wac;

  /**
   * Configure the MockWebApplicationContext and
   * set it as an attribute on the ServletContext.
   *
   * @throws Exception
   */
  protected void setUp() throws Exception {
    super.setUp();  // ensure super initialized
    ActionMockObjectFactory factory =
      getActionMockObjectFactory();
    ServletContext sc =
      factory.getMockServletContext();
    wac = new MockWebApplicationContext(sc);
    sc.setAttribute(
      WebApplicationContext.
        ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
      wac
    );
  }

  protected MockWebApplicationContext
    getMockWebApplicationContext() {
    return wac;
  }
}

To show this extension in action I'll refactor the earlier
example's implementation class and test case.

Refactored getSearchService() method from SearchAction

SearchService getSearchService() {
   ServletContext sc = getServlet().getServletContext();
   WebApplicationContext wac =
      WebApplicationContextUtils.getWebApplicationContext(sc);
  
   return (SearchService) wac.getBean("searchService");
}

Refactored setUp() method from SearchActionTest

public class SearchActionTest 
   extends BasicSpringActionTestCaseAdapter {

   private SearchService service;
   private MockControl serviceCtrl;

   protected void setUp() throws Exception {
      super.setUp();
      serviceCtrl =
         MockControl.createControl(SearchService.class);
      service =
         (SearchService) serviceCtrl.getMock();

      MockWebApplicationContext wac =
         getMockWebApplicationContext();
      wac.addBean("searchService", service);
   }

   ...
}

Now that the web tier has been covered, let's move on to another
key component of an enterprise application, the data access
tier.

Testing JDBC

Most developers involved in enterprise Java development are
quite familiar with writing JDBC code to access a relational
database. It's a simple and easy to use API, but can be a source of
serious application problems if not written correctly. Given the
importance of the data access tier you'll want to apply the same
testing rigor as you would elsewhere in your application.

Testing a data access component in isolation generally means
testing it independently of a relational database. Although
performing integration tests with the real database is important,
ideally, you'll want to begin your testing efforts by focusing on
the logic of your DAO and not its interaction with an external data source.
Mockrunner, once again, provides you with the tools to create
isolated tests with a minimal amount of coding and configuration.
Let's look at a simple JDBC-based DAO implementation used to lookup
a User object and see how Mockrunner can be used to
test this class.

public class UserDaoImpl implements UserDao {

   private final static String SELECT_USER =
      "SELECT * FROM USER WHERE USER.USERNAME = ?";

   public User getUser(String username)
                             throws DaoException {
      Connection conn = null;
      PreparedStatement ps = null;
      ResultSet rs = null;
      try {
         conn = getDataSource().getConnection();
         ps = conn.prepareStatement(SELECT_USER);
         ps.setString(1, username);
         rs = ps.executeQuery();
         User user = null;
         if (rs.next()) {
            user = new User();
            user.setId(rs.getInt("ID"));
            user.setUsername(rs.getString("USERNAME"));
            user.setFirstname(rs.getString("FIRSTNAME"));
            user.setLastname(rs.getString("LASTNAME"));
            user.setEmail(rs.getString("EMAIL"));
         }
         return user;
      } catch (Exception e) {
         String msg = "The user could not be retrieved.";
         throw new DaoException(msg, e);
      } finally {
         try {
            if (rs != null) {
               rs.close();
            }
            if (ps != null) {
               ps.close();
            }
            if (conn != null && !conn.isClosed()) {
              conn.close();
            }
         } catch (SQLException sqle) {
            String msg = "Could not close resources.";
            throw new DaoException(msg, sqle);
         }
      }
   }

   private DataSource getDataSource() throws Exception {
      InitialContext context = new InitialContext();
      return (DataSource) context.lookup("jdbc/ExampleDS");
   }
}

Despite the simplicity of the example, it still illustrates
several concerns common to most JDBC code. In particular, you'll
want to ensure the correct SQL is being executed, the data returned
from the query is being properly mapped into the domain object, and
you'll want to ensure all JDBC resources are being properly
released. Begin by creating a new test case extending from
BasicJDBCTestCaseAdapter. This adapter class, like the
test case adapter used with actions, provides a wrapper around the
underlying test module and its related mock object factory -- in
this case JDBCTestModule and
JDBCMockObjectFactory, respectively. The DAO is
looking up a DataSource bound to a JNDI context, so
you'll begin by adding this binding in the setUp()
method.

public class UserDaoJdbcTest 
      extends BasicJDBCTestCaseAdapter {

   private UserDaoJdbc dao;
  
   protected void setUp() throws Exception {
      super.setUp();
      JDBCMockObjectFactory factory =
         getJDBCMockObjectFactory();
      MockDataSource ds = factory.getMockDataSource();
      dao = new UserDaoJdbc();
      MockContextFactory.setAsInitial();
      InitialContext context = new InitialContext();
      context.rebind("jdbc/ExampleDS", ds);
   }

   protected void tearDown() throws Exception {
      super.tearDown();
      MockContextFactory.revertSetAsInitial();
   }

   ...
}

A MockDataSource reference is obtained from the
JDBCMockObjectFactory class and is bound to an
InitialContext. The call to
MockContextFactory.setAsInitial() configures the
factory as the JNDI provider for the test. With the test fixture
configured you can move on to test the getUser()
method.

public void testGetUser() {
    createResultSet();
   
    User user = dao.getUser("foobar");
    String sql =
       "SELECT * FROM USER WHERE USER.USERNAME = ?";
    verifySQLStatementExecuted(sql);
    verifyAllResultSetsClosed();
    verifyAllStatementsClosed();
    verifyConnectionClosed();
   
    // verify that all values have been properly
    // mapped to domain object
    assertEquals(1, user.getId());
    assertEquals("foobar", user.getUsername());
    assertEquals("Foo", user.getFirstname());
    assertEquals("Bar", user.getLastname());
    assertEquals("foo@bar.com", user.getEmail());
}

private void createResultSet() {
    PreparedStatementResultSetHandler handler
        = getPreparedStatementResultSetHandler();
    MockResultSet rs = handler.createResultSet();
    rs.addColumn("ID", new Object[]{"1"});
    rs.addColumn("USERNAME", new Object[]{"foobar"});
    rs.addColumn("FIRSTNAME", new Object[]{"Foo"});
    rs.addColumn("LASTNAME", new Object[]{"Bar"});
    rs.addColumn("EMAIL", new Object[]{"foo@bar.com"});
    handler.prepareGlobalResultSet(rs);
}

The first line of code in the test is a call to the private
createResultSet() method. This method creates an
instance of MockResultSet which will be returned when
the executeQuery() method is called on the
PreparedStatement. Mockrunner allows you to bind this
ResultSet either locally, meaning to a specific SQL
query, or globally so it will be the ResultSet
returned regardless of the query executed. Since there is only a
single SQL query to be executed, the global binding is
sufficient.

The getUser() method is invoked and its results are
verified. Specifically, it is important to verify the correct SQL
statement was executed and that all of the JDBC resources have been
properly closed and released. Finally, a quick check is made to
ensure the values contained in the ResultSet have been
properly mapped into the User object.

You now have a complete unit test to exercise the DAO logic that
can be run without the need for a container or database.

Conclusion

Hopefully this article has provided you with a useful
introduction to the Mockrunner framework. Although I've provided
several common usage examples, this article has only shown only a
small subset of the complete capabilities of the framework.
Thankfully, the core distribution ships with several useful
examples of technologies not covered in this introduction as well
as some further approaches to the topics already addressed. I think
you'll find Mockrunner to be a useful addition to your test-driven
development toolbox.

Resources

width="1" height="1" border="0" alt=" " />
Bob McCune is a General Partner with Twin Cities-based OpenPrinciple Consulting.
Related Topics >> Testing   |