Software Development, Agile Practices, Lean Thinking, Music
[ start | index | login or register ]
start > Unit Testing with StrutsTestCase and EasyMock

Unit Testing with StrutsTestCase and EasyMock

Created by brandon. Last edited by brandon, 2 years and 214 days ago. Viewed 880 times. #9
[diff] [history] [edit] [rdf]
labels
attachments

Background

A common problem I have observed with Java web development using the Struts framework is the inability to easily unit test your Struts Actions. Even more difficult is the task of testing the Struts flow control without a running Servlet container (E.g. Tomcat, Jetty, WebSphere, etc.).

I have recently rolled the sleeves up and decided to dig into this for the Reference Implementation I am building for my organization. This Reference Implementation utilizes Struts and subsequently I am aiming to embody many best practices, including unit testing at every layer of the architecture.

The primary benefits of unit testing your Struts Actions are:

  • Support for Test Driven Development (Red, Green, Refactor!)
  • Assurance of flow control
  • Confidence in refactoring for both Actions and ActionForms
  • The unit test serves as a documented contract of the Action behavior
  • Lighter weight development environment, no container needed

Mocking to the Rescue

The concept of mocking drives this effort. The StrutsTestCase technology provides this via the MockStrutsTestCase class. In addition EasyMock provides a quick and concise way to specify the mock classes that are used from within your Struts Action.

For each unit test I have taken a two part testing approach:

  • Create a mock object using EasyMock to assert the behavior of the service implementation class that is being executed by the Struts Action.
  • Create a mock object using MockStrutsTestCase of the Struts environment to assert that the correct ActionForward is returned.

The Abstract Factory Design Pattern

In order to enable substitution of the service class I have applied the >>Abstract Factory pattern. The TestObjectFactory class is a concrete implementation of an abstract ObjectFactory class. At the time of unit testing I can easily insert a stub or mock service, while at runtime the SpringObjectFactory concrete implementation will provide the actual services from the Spring application context of the web application.
public abstract class ObjectFactory
{
    private static ObjectFactory instance;
    public abstract Object getBean(String beanName, ServletContext servletContext);
    public static synchronized ObjectFactory getInstance()
    {
        if (instance == null)
        {
            instance = new SpringObjectFactory();
        }

return instance; } public static synchronized void setInstance(ObjectFactory instance) { ObjectFactory.instance = instance; } } … public class TestObjectFactory extends ObjectFactory { private static HashMap beans = new HashMap();

public Object getBean(String beanName, ServletContext servletContext) { return beans.get(beanName); } public void addBean(String beanName, Object bean) { beans.put(beanName, bean); } } … public class SpringObjectFactory extends ObjectFactory {

public Object getBean(String beanName, ServletContext servletContext) { WebApplicationContext webContext = WebApplicationContextUtils .getWebApplicationContext(servletContext);

return webContext.getBean(beanName); } }

StrutsTestCase and MockStrutsTestCase

Example:
public class ReservationPersistActionTest extends MockStrutsTestCase
{
	private TestObjectFactory factory;

public void setUp() throws Exception { super.setUp(); factory = new TestObjectFactory(); ObjectFactory.setInstance(factory); setContextDirectory(new File("WebContent")); }

public void testExecute_CreateSuccess() { // create our mock service MockControl control = MockControl.createControl(IReservationService.class); IReservationService reservationService = (IReservationService) control.getMock();

// record expected mock state, behavior, and return value ReservationForm reservationForm = new ReservationFormStub(); IReservationBO reservation = new ReservationBO(); ((ReservationFormStub)reservationForm).setReservationBO(reservation); reservationForm.setOperation("create"); reservationService.create(reservation); ReservationTR transactionResult = new ReservationTR(); transactionResult.setSuccess(true); control.setReturnValue(transactionResult);

// replay state, activate mock for use control.replay();

// put the mock service into the test object factory // which is used by my struts Action factory.addBean( "reservationService", reservationService);

// Call the MockStrutsTestCase methods to specify URL path, form, etc. setRequestPathInfo("/reservation/persist"); setActionForm(reservationForm); actionPerform(); // Assert the action's forward verifyForward("success");

// Assert the mock service behaved as recorded control.verify(); } ...

References

Icon-Comment anurekha_t, 3 years and 9 days ago. Icon-Permalink

Brandon,

In the example you have given, can you give some more details on the ObjectFactory and the TestObjectFactory? Basically, I'm not able to understand how you are making your action class use mocked reservationService object.

If I understand correctly, this is a framework which will not need any mock container for mocking the app server APIs. Correct me if wrong.

Request your immediate clarification on this.

thanks, Anu.

Icon-Comment perkar, 2 years and 319 days ago. Icon-Permalink

Brandon,

I agree with Anu. I do not really understand the ObjectFactory thing.

I have read somewhere else that a good solution could be to use aop to be able to set your service objects as mock.

I have tried using spring AOP, but Im not very pleased with my solution.

Do you have any suggestion to be able to set your service mock object in a nice way. I can see you are using ObjectFactory. Is that a class you have created yourself? In that case, is it a static class? How is it possible to use that one in your Action class?

Lots of questions :)

thanks, Pelle

Icon-Comment dennilee, 2 years and 221 days ago. Icon-Permalink

Obviously this does't work. You didn't cover the most important part. How to inject the mock.

Icon-Comment brandon, 2 years and 214 days ago. Icon-Permalink

To help clarify how the mock service is substituted for the real service I have added a section dealing with how this is accomplished using the Abstract Factory pattern.

Thanks for the questions!

Brandon

Icon-Comment nodje, one year and 187 days ago. Icon-Permalink

I just registered to say brilliant! … and simple.

thanks a lot brandon!

you saved my ass on this one, I was preparing to use the Spring's Struts Plugin , DelegatingActionProxy (refactoring all my actions, declaring them twice) and use AOP to control the injected mocks… not simple

I'd just add to the article that you have to replace your getBean(...) implementation with the ObjectFactory.getBean(...) one. Which is just changing one line if you have a BaseAction that all your Struts 1 action extends…

Please login to post a comment.
Describe here what your SnipSnap is about!

Configure this box!

  1. Login in
  2. Click here: snipsnap-portlet-2
  3. Edit this box
www.brandonburk.com | Copyright 2008 Brandon N. Burk