Use mock objects for RunData and Context

Using mock objects, you can test Turbine action methods directly without having Turbine running. Mock objects are simple implementations of an interface in the system to monitor operations on the objects. In Turbine, VeloctyAction classes have methods with two objects passed, RunData and Context. By passing mock objects to our action event method, we can call the method directly from within a test case and exercise our action code. In this example, we'll test the simplest method, doPerform from the sample application in the TDK. This is code directly from the SQL action class.

public void doPerform(RunData data, Context context)
    throws Exception
{
    data.setMessage("Can't find the button!");
}

To test the doPerform method, we need to pass a mock RunData and Context object to the method. We should be able to inspect the RunData object and verify that the setMessage() method was called and the correct text was passed. The Mock Objects project provides a set of classes to aid in implementing mock objects for testing. This example uses the EasyMock package.

package org.mycompany.newapp.modules.actions;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.framework.Test;

import org.easymock.EasyMock;
import org.easymock.MockControl;

import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.actions.VelocityAction;

public class SQLTest extends TestCase
{
    public SQLTest(String name)
    {
        super(name);
    }

    public static Test suite()
    {
        return new TestSuite( SQLTest.class );
    }

    public void testDoPerform() throws Exception
    {
        /* Create a control and mock RunData object */
        MockControl rundataCtl = EasyMock.controlFor(RunData.class);
        RunData rundata = (RunData) rundataCtl.getMock();

        /* Set up our mock RunData */
        rundata.setMessage("Can't find the button!");

        /* Create our action to test */
        VelocityAction action = new SQL();
        assertNotNull(action);

        /* Activate the RunData object, call the method, verify results */
        rundataCtl.activate();
        action.doPerform(rundata, null);
        rundataCtl.verify();
    }
}

In the setUp() method we configure the mock RunData object to expect one call to the setMessage() method with the correct text. We then create the action, call doPerform() passing our mock RunData object and verify that contents of the mock after the action has executed. If the setMessage() method was called with the incorrect parameter, or called more or less than one time, the test will fail.

The mock object for RunData could have been implemented with the Mock Objects library and compiled with the test code. Having the mock objects coded this way makes the test case simpler and the test will run slightly faster than using EasyMock. The trade off is another source file to maintain and update when the Turbine interface changes. Using EasyMock, the test cases are more complicated to setup with the control objects, but there is no additional source code to write other than the test.