Tuesday, April 26, 2011

Testing Struts 2 Actions with JUnit

I've been trying my best to stick closely to the Test-Driven Development process with my current project.  A couple of my previous blog posts detail my experiences setting up JUnit and later testing Hibernate code with JUnit.

I'm currently working with Struts 2 and I have been researching methods for testing my Actions.  The struts.apache.org website has a nice little tutorial detailing the StrutsTestCase and the Struts 2 JUnit plugin.  I searched for more online resources on this topic but it became quickly apparent that most examples include Stuts 2, JUnit and Spring.  I'm not using Spring in this project and I haven't had the opportunity to get up to speed on the Spring framework yet.  Therefore, a lot of the examples I found were not helpful for me right now.

One concern I had about using the StrutsTestCase is that its Sourceforge project page hasn't been updated in years.  It still proudly displays "Now supporting Struts 1.2 and 1.3!"  However, I decided to give it a try since the Struts 2 documentation offers a tutorial and the struts2-junit-plugin-2.2.1.1.jar is included in the current Struts 2 distribution.

I modified the sample code from the Struts 2 tutorial to suit my needs.

public void testCreateTagFail() throws Exception {
    request.setParameter("name", "");
    ActionProxy proxy = getActionProxy("/createtag.action");
    TagAction tagAction = (TagAction) proxy.getAction();
    proxy.execute();

    assertTrue("Problem There were no errors present in fieldErrors but there should have been one error present", tagAction.getFieldErrors().size() == 1);
    assertTrue("Problem field 'name' not present in fieldErrors but it should have been",
            tagAction.getFieldErrors().containsKey("name") );

}

I had some compile errors and needed to add spring-test-2.5.6.jar and spring-core-2.5.6.jar to my build path.  Both of these jars are included in the Struts 2.2.1.1 distribution.

The method above is quite simple and just tests the Action's validation() method to confirm it returns a FieldError when a value for the Tag Name is missing.

Below is a method that gave me a lot of trouble:
    public void testCreateTag() throws Exception {

        request.setParameter("name", "my tag name");
        ActionProxy proxy = getActionProxy("/createtag.action");
        TagAction tagAction = (TagAction) proxy.getAction();
        String result = proxy.execute();

        assertTrue("Problem There were errors present in fieldErrors but there should not have been any errors present", tagAction.getFieldErrors().size() == 0);
        assertEquals("Result returned form executing the action was not success but it should have been.", "success", result);
    }

This JUnit test caused the Action to throw a Null Pointer exception when I tried to run it.  The problem is the Action expects to find a Hibernate SessionFactory object in the ServletContext.  (This earlier blog post explains my methodology for adding the Hibernate SessionFactory to the ServletContext.)  Since the StrutsTestCase is running outside a real container, using a mock container, the Action is not finding the SessionFactory and returning Null instead.

I did some research and determined I had a few options to resolve this problem.  First, as I mentioned in a previous blog post, I would be better off using mock database objects or DBUnit rather than testing against Hibernate and my actual relational database.  I may do that in the future, but it's not going to happen now.  :)

Another option would be to add the SessionFactory to the mock ServletContext and pass that to the Action through the StrutsTestCase.  This is an option I probably should have pursued and I would love to see some example code for this.

Of course, I went with the third and probably least elegant option.  I created a base class that extends ActionSupport and added a method getSession() which tries to retrieve the the Hibernate SessionFactory from the ServletContext and creates a new one if it fails.
public class FlashCardsAppBaseAction extends ActionSupport{

    protected Session getSession() {

        //try to get hibernate session from the servlet context
        SessionFactory sessionFactory =

        (SessionFactory) ServletActionContext.getServletContext()
            .getAttribute(FlashCardAppConstants.HibernateSessionFactoryName);

        // if the sessionFactory is null then create a new one
        if (sessionFactory == null) {
            sessionFactory = HibernateUtil.createSessionFactory();

            // add the Hibernate SessionFactory to the ServletContext
            ServletActionContext.getServletContext().setAttribute(FlashCardAppConstants.HibernateSessionFactoryName, sessionFactory);
                }

        // get a session from the session factory
        return sessionFactory.openSession();
        }
}

I refactored my Actions to extend the new FlashCardsAppBaseAction and they get a handle to the SessionFactory using the super.getSession() method as seen below.

@SkipValidation
public String listTags() {

    try {

        // get a handle to a Hibernate session
        Session session = getSession();

        // get a list of Tags from the data tier
        TagPersister tagPersister = new TagPersister();

        tagList = tagPersister.getTags(session);

        return "success";

    } catch (HibernateException e) {

        logger.error("Exception in listTags():", e);
    
        return "input";
    }
}

The code now works when running in an actual servlet container and also through the mock container via the JUnit tests.

One last note, my new StrutsTestCase tests ran successfully through the Eclipse IDE but were failing when run through ANT outside of Eclipse.  I fixed this by adding the Tomcat jars to the Classpath for the Ant build.xml.

2 comments:

  1. This is the best explanation I have seen so far on the web. I was looking for a simple yet informative about this topic finally your site helped me a lot to gain knowledge.
    Best selenium training in chennai
    selenium Classes in chennai

    ReplyDelete
  2. Very true and inspiring article. I strongly believe all your points. I also learnt a lot from your post. Cheers and thank you for the clear path.
    Software testing training
    Software training

    ReplyDelete