Recent articles:
Popular archives:
Java: A platform for platforms
Sun's reorg may seem promising to shareholders but it's also a scramble for position. The question now is whether Sun can,
or wants to, maintain its hold on Java technology. Especially with enterprise leaders like SpringSource and RedHat investing
heavily in Java's future as a platform for platforms
Also see:
Discuss: Tim Bray on 'What Sun Should Do'
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.
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:
MockStrutsTestCase so that it simulates the servlet container.
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.
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.
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.
Archived Discussions (Read only)