Unit test Struts applications with mock objects and AOP

How AOP complements OOP to bridge the integration gap

Test-driven development improves software quality while reducing development efforts. As the foundation of an overall test strategy, unit tests must be comprehensive, easy to set up, and quick to execute. However, the dependency on the execution environment and on code outside the class under test complicates our ability to achieve these goals. Deploying the application in a container significantly slows down the code-and-test cycle. Furthermore, the need to collaborate with other classes usually leads to more complex test setups and slower test runs.

Integrating two popular test frameworks, StrutsTestCase and EasyMock, to unit-test Struts applications leads to easier test setups and faster test runs. However, these two frameworks leave a gap that prevents an ideal integration. In this article, I examine both an object-oriented solution and an aspect-oriented solution to this problem. The comparison also demonstrates how aspect-oriented programming (AOP) complements object-oriented programming (OOP) by simplifying the solution to a seemingly difficult problem.

The integration need

Non-trivial Struts applications exhibit both execution environment and class dependencies because Struts actions execute in a servlet container and typically call other classes to process the requests. The mock object testing approach helps remove the unwanted dependencies. The StrutsTestCase testing framework provides a mock implementation of the servlet container with the MockStrutsTestCase class that extends the base JUnit test case. It facilitates out-container testing that speeds up unit testing cycles. EasyMock, another testing framework, makes it easy to dynamically mock the collaborating classes. The mocks substitute real classes with simpler implementations and add verification logic to support unit testing.

Clearly, it is advantageous to combine the two frameworks so that Struts applications can be tested in true isolation. Ideally, you want to implement such a unit test with the following steps:

  1. Set up MockStrutsTestCase so that it simulates the servlet container.
  2. Mock the class that the action depends on using EasyMock.
  3. Set mock expectations.
  4. Inject the mock into the action under test.
  5. Proceed with the test and verifications.

Step 4 performs dependency injection that steers the Struts action under test away from its real collaborator to interact with the mocked one. To inject the mock generated by EasyMock into actions, you need access to action instances in the test classes. Unfortunately, this presents an obstacle, as access is not easily obtained from MockStrutsTestCase.

The OOP solution

How can you access the action instances from MockStrutsTestCase? Let's look at the relationships between MockStrutsTestCase and the controller components of Struts.

Figure 1 highlights the key relationships that could potentially lead to a solution.

Figure 1. Relationships that could lead to an OOP solution. Click on thumbnail to view full-sized image.
  • MockStrutsTestCase has a public getter method for retrieving ActionServlet.
  • ActionServlet has a protected getter method for RequestProcessor.
  • RequestProcessor stores the action instances as a protected member.

Can you subclass both ActionServlet and RequestProcessor to provide MockStrutsTestCase the access to the actions? The resulting call chain would be: myActionTest.getActionServlet().getRequestProcessor().getActions().

This approach doesn't work when you look at the sequence of calls that link MockStrutsTestCase to Struts actions.

Figure 2 illustrates the key interactions between MockStrutsTestCase and Struts components.

Figure 2. Interactions between MockStrutsTestCase and Struts components. Click on thumbnail to view full-sized image.

The problem, as shown in Figure 2, involves the timing of Struts action creation. Mock injection into the actions needs to happen before the call to MockStrutsTestCase.actionPerform(). However, the actions are not yet available because only after the call to actionPerform() does RequestProcessor create the action instances.

Since you cannot easily propagate the action instance to MockStrutsTestCase, why not just subclass RequestProcessor and override the processActionCreate() method? In the overridden method, you have access to all the action instances, so creating, configuring, and setting a mock to the right action instance becomes straightforward. Because MockControl.verify() should be called after actionPerform(), you also need to override processActionPerform() to make the verification call.

This solution is impractical for testing non-trivial Struts applications. Even if all actions interact with a single mock, testing one action would likely require multiple test methods, each with different mock expectations. The proposed solution would end up creating different RequestProcessor subclasses, each setting different mock expectations. Multiple Struts configuration files are also needed to specify the different RequestProcessor subclasses. Managing a large number of tests would become a headache.

The AOP solution

Thus, somehow making the action instance available to MockStrutsTestCase before the action executes is still desirable. If you are familiar with AOP, you recognize the simple solution that directly maps to this requirement. The key is to define a pointcut that captures the action execution join point and then a before advice to inject the mock into the action.

Here, I chose AspectJ to implement the solution. Other AOP implementations such as Spring AOP should work as well. Spring AOP would require one extra step that delegates Struts action management to Spring with Spring's DelegatingActionProxy.

Figure 3 shows the static model of the unit test example with the AOP-based solution.

Figure 3. Static model of the unit test example with the AOP-based solution. Click on thumbnail to view full-sized image.

SimpleAction is a subclass of a Struts action and collaborates with ActionService. SimpleActionTest derives from MockStrutsTestCase to test SimpleAction.

SimpleActionTest creates and sets up a mock ActionService using EasyMock. SimpleActionTest also implements the StrutsActionPreExecuteListener interface to receive notification when SimpleAction's execute method is about to run. As part of the notification, SimpleActionTest receives the SimpleAction instance to inject the ActionService mock. It is the aspect class StrutsActionPreExecuteNotifier that notifies any test class that implements the listener interface and makes the action instance available.

Here is how StrutsActionPreExecuteNotifier is implemented:

  • First, a pointcut selects the test method execution join point. The test method resides in the test class that listens for the action pre-execute event. The pointcut also exposes the current executing test class object:
    pointcut mockStrutsTest(StrutsActionPreExecuteListener actionTest):
        execution(public void StrutsActionPreExecuteListener+.test*())
        && this(actionTest);
    
  • Next, a second pointcut captures the action execution join point. Combining it with the first pointcut, the matching scope is limited to within the call flow of the action test method. The reduced scope filters out action execution that is not triggered by the test methods. As a result, the aspect does not affect production code. Both the action and its corresponding test class instances are exposed via pointcut parameters:
    pointcut strutsActionExecute(Action action, StrutsActionPreExecuteListener actionTest):
        execution(public ActionForward Action+.execute(..)) &&
        this(action) &&
        cflow(mockStrutsTest(actionTest));
    
  • Finally, a before advice associated with the previous pointcut notifies the test classes, who are listeners of the action event, and passes along the action instance for mock injection:
    before(Action action, StrutsActionPreExecuteListener actionTest):
        strutsActionExecute(action, actionTest) {
        actionTest.preActionExecuteOccurred(action);
    }
    

Figure 4 illustrates the dynamic interactions among the classes.

Figure 4. Dynamic interactions among the classes. Click on thumbnail to view full-sized image.

The dotted line going from the action to the aspect represents the capture of the action execution join point. Comparing this second sequence diagram to the first one, the significant differences are the three steps occurring just before action execution:

  1. A pointcut captures the action execution join point indicated by the dashed arrow going from SimpleAction to StrutsActionPreExecuteNotifier.
  2. The aspect's before advice notifies the test class and passes the corresponding action instance to it.
  3. The test class injects the mock object into the action instance that is about to start executing.

You can now proceed with writing the action tests following the five steps outlined previously. The code below shows the partial code listing for SimpleActionTest, with inline comments highlighting each step:

Partial listing of the action test using both MockStrutsTestCase and EasyMock

public class SimpleActionTest extends MockStrutsTestCase 
     implements StrutsActionPreExecuteListener {

    // 2. Mock the class that Action depends on
    private MockControl mockControl = MockControl.createControl(ActionService.class);
    private ActionService serviceMock = (ActionService)mockControl.getMock();

    // 1. Setup MockStrutsTestCase
    protected void setUp() throws Exception {
        super.setUp();
        setRequestPathInfo("/action/simpleAction");
    }
    
    protected void tearDown() throws Exception {
        super.tearDown();
        mockControl.reset();
    }

    // 4. Inject the mock into the Action
    public void preActionExecuteOccured(Action action) {
        ((SimpleAction)action).setService(serviceMock);
    }
    
    public void testSuccess() {
        // 3. Set mock expectations
        serviceMock.serveAction();
        mockControl.setReturnValue(true);
        mockControl.replay();

        // 5. Proceed with test and verifications
        actionPerform();
        verifyForward("success");
        verifyNoActionErrors();
        mockControl.verify();
    }
    
    public void testFailure() {
    // details skipped
    }
}

Four possible multiplicity relationships exist between actions and the services they depend on:

  • Each action depends on one service.
  • Each action depends on multiple services.
  • Multiple actions depend on one service.
  • Multiple actions depend on multiple services.

The solution I present here has the flexibility to support all four scenarios with relative ease because mock creation, expectation setup, and mock injection can all be performed in the individual test classes.

Can you avoid using the listener interface so that mock injection is done inside StrutsActionPreExecuteNotifier? It seems to make test class implementation even simpler. However, similar to the earlier OOP solution, writing multiple aspects to create different mocks and set up different mock expectations would prove necessary. Localizing the mock creation and setup in individual test classes as made possible by the listener approach is more convenient.

The power of AOP

Someone may come up with a good OOP solution to our integration problem. However, it would likely require in-depth understanding of Struts and StrutsTestCase, and a non-trivial amount of effort. The integration gap between the two testing frameworks is the difficulty in gaining access to the Struts action instance before its execution. After identifying the fundamental cause, the AOP solution naturally emerges from the problem description. Instead of the more complex solution required with traditional OOP, AOP allows us to map our solution more closely to the problem space.

AOP's magic lies with its join point model. It lets you cut through intermediaries, such as ActionServlet and RequestProcessor, and go straight to the heart of the problem. The economical way of addressing crosscutting concerns gives developers the expressive power to design intuitive and simpler solutions. AOP is a powerful programming approach that fills the holes left by traditional OOP. When applied correctly to the right type of problems, it improves code modularity, resulting in code that is cleaner and easier to understand. Hopefully, this article not only helped you unit test your Struts applications, but also gave you new appreciation for some of the significant benefits that AOP offers.

Walter Jia is an independent consultant specializing in enterprise application development. He is always on the lookout for better solutions while taking a pragmatic approach to helping clients build quality software and avoid unnecessary costs. He is based in Vancouver Canada.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more