ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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:

  1. 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.

  2. The client sends the request to the server-side Cactus redirect servlet.

  3. 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.

  4. The redirect servlet calls the setUp() method in the test case to satisfy the test precondition and calls testXXX() to launch the test process.

  5. The request is redirected to the Struts RequestProcessor.

  6. RequestProcessor uses the same test case instance and calls prepareFromXXX() and prepareActionXXX() to prepare the ActionForm and Action instance.

  7. The RequestProcessor calls the execute() method in Action.

  8. The RequestProcessor calls endActionXXX() method in the test case to do any necessary verification and prepare the next join point, if needed.

  9. The Struts framework finishes the remaining operations and returns the control flow.

  10. The Cactus redirect servlet calls the tearDown() method in the test case to clear the test environment.

  11. The Cactus redirect servlet finishes the test case invocations.

  12. The Cactus redirect servlet returns the response to client-side test runner.

  13. 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
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);
      }
   }
} 

Pages: 1, 2, 3, 4, 5

Next Pagearrow