Unit Testing Struts 1.2 with StrutsTestCase, EasyMock and Aspects

Posted on Thursday, June 11, 2009

4


We have a legacy application and a lot of presentation code is written using Struts 1.2.4. For unit tetsing the action classes we used the following approach.

StrutsTestCase provides both a Mock Object approach and a Cactus approach  to actually run the Struts ActionServlet, allowing you to test your Struts code with or without a running servlet engine. When you want to execute your tests as a part of the continuous integration environment in which all the unit tests should execute without deploying the application to the container.

For the following entry of Struts-config.xml

<action path="/login" type="com.dnbi.simpleaction.LoginAction" name="loginForm" input="/login/login.jsp" scope="request">
			<forward name="success" path="/action/simpleAction" />
			<forward name="failure" path="/failurePath" />
		</action>

and for the following action,


public class LoginAction extends Action {

	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {

		ActionErrors errors = new ActionErrors();

		String username = ((LoginForm) form).getUsername();
		String password = ((LoginForm) form).getPassword();

		if ((!username.equals("vikas")) || (!password.equals("pass")))
			errors.add("password", new ActionError("error.password.mismatch"));

		if (!errors.isEmpty()) {
			saveErrors(request, errors);
			return new ActionForward(mapping.getInput());
		}

		HttpSession session = request.getSession();
		session.setAttribute("authentication", username);
		return mapping.findForward("success");

	}

the test would look like this


public class LoginActionTest extends MockStrutsTestCase {

	public LoginActionTest(String testName) {
		super(testName);
	}

	public void setUp() throws Exception {
		super.setUp();
		setInitParameter("validating", "false");
		setRequestPathInfo("/login");
	}

	public void testLogin_WithoutParameters_Expected_BackToLoginPage() {
		actionPerform();
		assertEquals(null, getSession().getAttribute("authentication"));
		verifyForwardPath("/login/login.jsp");
		verifyActionErrors(new String[] { "error.username.required",
				"error.password.required" });
	}

	public void testSuccessfulLogin() {

		addRequestParameter("username", "vikas");
		addRequestParameter("password", "pass");
		setRequestPathInfo("/login");
		actionPerform();
		verifyForward("success");
		verifyForwardPath("/action/simpleAction");
		assertEquals("vikas", getSession().getAttribute("authentication"));
		verifyNoActionErrors();
	}

	public void testFailedLogin() {

		addRequestParameter("username", "deryl");
		addRequestParameter("password", "express");
		setRequestPathInfo("/login");
		actionPerform();
		verifyForwardPath("/login/login.jsp");
		verifyInputForward();
		verifyActionErrors(new String[] { "error.password.mismatch" });
		assertNull(getSession().getAttribute("authentication"));
	}

	public static void main(String[] args) {
		junit.textui.TestRunner.run(LoginActionTest.class);
	}

Now comes the difficult part which would be used in most of the scenarios. The action class would be calling another service/manager to get the work done. When you are doing a unit test, you would like not to make a call to the actual service but instead make a call to the mocked service.

So assume the following action class

Here the action class makes a call to the DoSomethingService. What we would like to do is to mock the service in our test case so that the actual service is not called.

public class ServiceCallingAction extends Action {

	private DoSomethingService service;

	private DoSomethingService getService() {
		return this.service;
	}

	public void setService(DoSomethingService service) {
		this.service = service;
	}

	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {

		return mapping.findForward(getService().serveAction() ? "success"
				: "failure");
	}

}

This becomes difficult with StrutsTestCase because we do not get a handle to the action class. The action class is prepared behind the scenes and is called on the basis of the definition in the struts-config.xml file.

So for example in the test case we are calling the method actionPerform() As soon as this method is called then on the basis of setRequestPathInfo() the action class is prepared and executed behind the scenes. So how do we inject the mocked object.

This is where Aspects help us. So we can ask the aspects to inject the mock before the execute method on the action is called.

So let us see what the aspect would look like

aspect InjectMockService {

	pointcut mockStrutsTest(StrutsActionMockServiceInjection actionTest):
        execution(public void StrutsActionMockServiceInjection+.test*()) &&
        this(actionTest);

	pointcut strutsActionExecute(Action action,
			StrutsActionMockServiceInjection actionTest):

        execution(public ActionForward Action+.execute(..)) &&
        this(action) &&
        cflow(mockStrutsTest(actionTest));

	before(Action action, StrutsActionMockServiceInjection actionTest):
    	strutsActionExecute(action, actionTest) {

		actionTest.injectMockService(action);
	}

now as you can see in the aspect before the execute method on the action is called, we call the injectMockService of the ActionTestClass.

Let us see what the action test class would look like

public class ServiceCallingActionTest extends MockStrutsTestCase implements
		StrutsActionMockServiceInjection {

	// ================Create any mock service objects which the action would be
	// calling ==========
	private DoSomethingService serviceMock = createMock(DoSomethingService.class);

	// ================ SETUP & TEARDOWN ========================
	protected void setUp() throws Exception {
		super.setUp();
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

	// ================ INJECT MOCK OBJECTS To the Action==========
	public void injectMockService(Action action) {
		((ServiceCallingAction) action).setService(serviceMock);
	}

	// ========================= TEST CASES =======================
	public void testSuccess() {
		setRequestPathInfo("/action/simpleAction");
		expect(serviceMock.serveAction()).andReturn(true);
		replay(serviceMock);
		actionPerform();
		verifyForward("success");
		verifyNoActionErrors();
		verify(serviceMock);
	}

	public void testFailure() {
		setRequestPathInfo("/action/simpleAction");
		expect(serviceMock.serveAction()).andReturn(false);
		replay(serviceMock);
		actionPerform();
		verifyForward("failure");
		verifyNoActionErrors();
		verify(serviceMock);
	}
}

so here before the execute method on the action is called, the injectMockService is called on the test class which injects the mocked service object to the action class. Once the mocked object has been injected, we can use the regular easymock syntax to run the test case against the mocked object.

References for this article

1. StrutsTestCase
2. EasyMock
3. AJDT for eclipse

4. Source code for this article