Unit Test Your Struts Application
Pages: 1, 2, 3, 4, 5
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 pointXXXin the test case, it calls thebeginXXX()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, andHttpSessionto the test case public fields.The redirect servlet calls the
setUp()method in the test case to satisfy the test precondition and callstestXXX()to launch the test process.The request is redirected to the Struts
RequestProcessor.RequestProcessoruses the same test case instance and callsprepareFromXXX()andprepareActionXXX()to prepare theActionFormandActioninstance.The
RequestProcessorcalls theexecute()method inAction.The
RequestProcessorcallsendActionXXX()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 pointXXX, and calls theend()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 theActionForm, which will be the argument theexecute()method in theActionclass.Use the
prepareActionXXX()method to prepare theActioninstance to be called.Use the
endActionXXX()method to do any necessary verification and prepare the next join point, if needed, after callingAction'sexecute()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);
}
}
}