Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Unit test Struts applications with mock objects and AOP

How AOP complements OOP to bridge the integration gap

  • Print
  • Feedback

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.

  • Print
  • Feedback

Resources