Unit Test Your Struts Application
A Simple Solution
Why not combine the two approaches together? Since Struts has done the job
of constructing the ActionMapping according to the Struts configuration file, it is
a good choice to leave the mapping construction job to Struts. What we need to do
is just to provide a join point around the execute()
method in the Action class that
is called by Struts. Test case writers can make use of this join point to prepare
the ActionForm and use traditional unit test technologies to
prepare an Action class that uses external interfaces.
The idea is to extend the Cactus framework's "in-container" part to interact with
the test case two times in the web container. One is called by the Cactus-specific
servlet, ServletRedirector, as usual. The other is called by the Struts framework. Because
Cactus and Struts are both running in the same JVM/web container, they can interact
with the same test case instance.
Introducing StrutsUT
The solution presented here, StrutsUT,
provides such an extension to help unit test Struts applications.
Here's how it works:
A client-side test runner creates the test case instance and initiates it by calling
the begin() method. For each test point XXX in the test
case, it calls the beginXXX() method
to prepare request parameters and/or request headers.
The client sends the request to the server-side Cactus redirect servlet.
The redirect servlet creates the test case instance on the server side according to
the information from request, and assigns the HttpServletRequest,
HttpServletResponse, and HttpSession to the test
case public fields.
The redirect servlet calls the setUp() method in the
test case to satisfy the test precondition and calls
testXXX() to launch the test process.
The request is redirected to the Struts RequestProcessor.
RequestProcessor uses the same test case instance and
calls prepareFromXXX() and
prepareActionXXX() to prepare the ActionForm
and Action instance.
The RequestProcessor calls the execute()
method in Action.
The RequestProcessor calls endActionXXX()
method in the test case
to do any necessary verification and prepare the next join point, if needed.
The Struts framework finishes the remaining operations and returns the control flow.
The Cactus redirect servlet calls the tearDown() method in the
test case to clear the test environment.
The Cactus redirect servlet finishes the test case invocations.
The Cactus redirect servlet returns the response to client-side test runner.
The client-side test runner calls the endXXX() method in the test
case to verify the response for each test point XXX, and calls the end()
method to clear the status of the test case.
Figure 2 shows the StrutsUT test case execution flow.

Figure 2. StrutsUT test case execution flow
With StrutsUT, test case writers now can do more in the test case:
Use prepareFormXXX() method to prepare the ActionForm, which will be the argument the execute() method in the Action class.
Use the prepareActionXXX() method to prepare the Action instance to be called.
Use the endActionXXX() method to do any necessary verification and prepare the next join point, if needed, after calling Action's execute() method.
Like the extra methods in Cactus' ServletTestCase--begin(), beginXXX(), endXXX(),
end(), setUp(), and tearDown()--it is not mandatory to provide these extra methods.
Use them when needed.
There are two implementations in StrutsUT to satisfy the idea described above.
The StrutsUT Traditional Solution
In order to insert such a join point within the control flow of Struts, it is
necessary to extend Struts' central controller,
RequestProcessor, to interact with the test
case. We also have to extend Cactus' test case base class,
ServletTestCase, to
add extra information about the test point name and test case instance that will be
used by the Struts central controller to call the correct test helper methods on the
exact test case instance.
StrutsUT replaces the Struts central controller,
RequestProcessor, with a subclass called
StrutsUnitTestRequestProcessor, and uses
StrutsServletTestCase to replace Cactus'
ServletTestCase as the test case base class.
A Simple Test Case
// SimpleStrutsTest.java
package unittest.struts;
import javax.servlet.RequestDispatcher;
import org.apache.cactus.WebRequest;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.easymock.MockControl;
import org.jingle.unittest.struts.*;
import unittest.simple.ExternalInf;
import com.meterware.httpunit.WebForm;
import com.meterware.httpunit.WebResponse;
public class SimpleStrutsTest
extends StrutsServletTestCase {
//define the mock object
MockControl controller = MockControl.
createControl(ExternalInf.class);
ExternalInf inf = (ExternalInf)
controller.getMock();
//make sure call the super.setup() when
//override this method
protected void setUp() throws Exception {
super.setUp();
}
//make sure call the super.tearDown()
//when override this method
protected void tearDown() throws Exception {
super.tearDown();
}
public void beginStrutsTestAction(
WebRequest request) {
}
//Prepare ActionForm
public ActionForm prepareFormStrutsTestAction(
ActionMapping mapping) {
SimpleForm form = new SimpleForm();
form.setName("Dennis");
return form;
}
//Prepare the Action
public Action prepareActionStrutsTestAction(
ActionMapping mapping) {
//define the behavior of mock object
controller.reset();
inf.doSomeExtThing(10);
controller.setReturnValue("Great");
controller.replay();
//Use override technology to bridge the
//mock object to the class to be tested
SimpleAction action = new SimpleAction() {
protected ExternalInf getExternalInf() {
return inf;
}
};
return action;
}
public void testStrutsTestAction() {
//forward to the action to be tested
RequestDispatcher rd = this.request
.getRequestDispatcher("/strutsTest.do");
try {
rd.forward(this.request, this.response);
} catch (Exception e) {
fail("Unexpected exception: " + e);
}
}
//verify the mock object after the execution
//of action
public ActionResult
endActionStrutsTestAction() {
controller.verify();
//continue the struts framework process
return null;
}
//compare the result html documents
public void endStrutsTestAction(
WebResponse response) {
try {
WebForm form = response.getForms()[0];
assertEquals(
"DennisGreat",
form.getParameterValue("name"));
} catch (Exception e) {
fail("Unexpected exception: " + e);
}
}
}