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

The Problems With Unit Testing a Struts Application

What is Struts?

Struts is a successful web-application framework that uses a central controller to control the page flow. The control logic is represented by the Struts configuration file. Refer to struts.apache.org to learn more about Struts.



Below are a simple Struts configuration file and its related Action and Form, as well as its JSP file.

struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>

  ...
  <form-beans>
    <form-bean 
              name="simpleForm"
              type="unittest.struts.SimpleForm"/>
  </form-beans>

  <action-mappings>
    <action    
              path="/strutsTest"
              type="unittest.struts.SimpleAction"
              name="simpleForm"
              scope="request">
     <forward 
              name="succeed" 
              path="/result.jsp"/>
    </action>
  </action-mappings>
  ...

</struts-config>


//SimpleForm.java
package unittest.struts;

import org.apache.struts.action.ActionForm;

public class SimpleForm extends ActionForm {
    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
} 

//SimpleAction.java
package unittest.struts;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import unittest.simple.ExternalInf;

public class SimpleAction extends Action {

    //Get the name from the form and put it 
    //into request attribute
    public ActionForward execute(
                    ActionMapping mapping,
                    ActionForm form,
                    HttpServletRequest request, 
                    HttpServletResponse response)
                    throws Exception {
        SimpleForm simpleForm = (SimpleForm)form;
        ExternalInf inf = getExternalInf();
        String name = simpleForm.getName();
        if (name == null) {
            name = "anonymous";
        }
        request.setAttribute("name", name + 
                         inf.doSomeExtThing(10));
        return mapping.findForward("succeed");
    }
    
    protected ExternalInf getExternalInf() {
        ExternalInf inf = null;
        //do some operation to get the 
        //external interface
        //Use JNDI operation or something else
        return inf;
    }
} 


//result.jsp

<%@ page contentType=
                "text/html; charset=UTF-8"%>
<%@ taglib uri="/WEB-INF/struts-bean.tld" 
           prefix="bean" %>

<html>
<body>
<form name="form1">
  The name is <input type="text" name="name" 
          value='<bean:write name="name"/>'/>
</form>
</body>
</html>

The Problems

Struts separates the view and controller from the old servlet approach. This helps developers to implement each function more clearly, but it presents some problems when doing unit tests. There are two approaches to do unit testing on Struts applications:

  • Using Cactus to test a single Action class

    It is possible to use Cactus to unit test the execute() method in the Action class. Test case writers can continue to use the mock object and override approaches. However, they have to construct the ActionMapping object by themselves. ActionMapping is a Struts class that is constructed by Struts itself, according to the content of the Struts configuration file. To ask the test case writers to take on this responsibility is not a good choice.

    Below is a test case for SimpleAction.

    //SimpleActionTest.java
    package unittest.struts;
    
    import org.apache.cactus.ServletTestCase;
    import org.apache.struts.action.ActionForward;
    import org.apache.struts.action.ActionMapping;
    import org.easymock.MockControl;
    
    import unittest.simple.ExternalInf;
    
    public class SimpleActionTest 
                            extends ServletTestCase {
    
       protected void setUp() throws Exception {
          super.setUp();
       }
    
       protected void tearDown() throws Exception {
          super.tearDown();
       }
    
       //test execute() method in SimpleAction
       public void testExecute() {
          //define the mock object
          MockControl controller = MockControl.
                    createControl(ExternalInf.class);
          final ExternalInf inf = (ExternalInf)
                                controller.getMock();
            
          //define the behavior of mock object
          inf.doSomeExtThing(10);
          controller.setReturnValue("Great");
          controller.replay();
    
          //create SimpleAction class
          //use override technology to bridge from 
          //mock object to the class
          //to be tested
          SimpleAction action = new SimpleAction() {
             protected ExternalInf getExternalInf() {
                  return inf;
             }
          };
          //prepare the SimpleForm
          SimpleForm form = new SimpleForm();
          form.setName("Dennis");
          //prepare the ActionMapping
          ActionMapping mapping=new ActionMapping();
          mapping.addForwardConfig(new ActionForward(
                                       "succeed", 
                                       "/result.jsp", 
                                       false));
           
          try {
             //do the test
             ActionForward forward=action.execute(
                                           mapping, 
                                           form, 
                                           request, 
                                           response);
             //compare the result
             assertNotNull(forward);
             assertEquals("/result.jsp", 
                          forward.getPath());
             //verify the mock object
             controller.verify();
          } catch (Exception e) {
             fail("Unexpected exception: " + e);
          }
       }
    } 
  • Using Cactus for an end-to-end test

    In some circumstances, there is a requirement to do an end-to-end test in Struts. For example, there is a test case to test whether a specific button in the response HTML document is disabled or not, via a certain URL. There are many limitations in this kind of test. Because the Struts framework controls the whole flow, from accepting the URL to returning the response, there is no "join point" to interact with the test case. It is impossible to use mock objects and overrides. This presents some difficulties for unit testing the Struts application using external interfaces.

Both of these approaches have serious limitations. The first approach uses traditional unit test technologies, but has limited scope. It can only test the Action class, which is only part of the Struts framework. The second approach has a larger scope and provides an end-to-end test solution, but cannot use the traditional unit test technologies. It is very difficult to write test cases without the help of these technologies.

Pages: 1, 2, 3, 4, 5

Next Pagearrow