Container-free Testing with Mockrunner Blog

Version 2



    What is a Mock Object?
    What is Mockru nner?
    Core Mockrunner Classes
    Testing Struts
       Extending Mockrunner
    Testing JDBC

    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.

    Test ModulesProvides the central runtime behavior of the framework.
    ActionTestModule EJBTestModule HTMLOutputModule JDBCTestModule ServletTestModule TagTestModule
    TestCase AdaptersExtensions of JUnit's TestCase that act as wrappers around the functionality of their underlying test modules.
    ActionTestCaseAdapter BasicJDBCTestCaseAdapter JMSTestCaseAdapter BasicTagTestCaseAdapter ServletTestCaseAdapter
    Mock Object FactoriesFactory 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'sTestCase. 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>TestCaseAdaptercontains a reference to the technology-specific test module and related mock object factory. Examples in the distribution includeBasicServletTestCaseAdapter andBasicEJBTestCaseAdapter.

    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 andJMSTestCaseAdapter.

    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("")); 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'ssearchCatalog() method and depending on the results returned from the service, the action will either populate the request with the list of results or an ActionMessageindicating no results were found.

    You'll begin by creating a subclass ofBasicActionTestCaseAdapter. This class provides a wrapper around the ActionTestModule andActionMockObjectFactory, which provide the functionality required for testing Struts Actions. TheSearchAction 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 useEasyMock 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. ThesetUp() 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'ssetUp() 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 itssearchCatalog() method be invoked and return aList containing one result. For the purposes of the test, the actual contents of the list are irrelevant. Next, theactionPerform() 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 noActionErrors 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 theMockActionMapping class. Once again, a call to invoke the actionPerform() method is made to run the action'sexecute() 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 theSearchAction.

    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 SpringWebApplicationContext. Obtaining a reference to this class can be done either by subclassing one of Spring's Action extensions or by using its WebApplicationContextUtilsclass. 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'sapplicationContext.xml. SinceWebApplicationContext 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 theBasicActionTestCaseAdapter 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 fromSearchAction

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

    Refactored setUp() method fromSearchActionTest

    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 DAOand 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 aUser 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 ( { 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 fromBasicJDBCTestCaseAdapter. 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 andJDBCMockObjectFactory, 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 theJDBCMockObjectFactory class and is bound to anInitialContext. The call toMockContextFactory.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("", 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[]{""}); handler.prepareGlobalResultSet(rs); }

    The first line of code in the test is a call to the privatecreateResultSet() method. This method creates an instance of MockResultSet which will be returned when the executeQuery() method is called on thePreparedStatement. Mockrunner allows you to bind thisResultSet either locally, meaning to a specific SQL query, or globally so it will be the ResultSetreturned 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.


    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.