Making a mockery of CQ5 with JMockit

Java mocking tools are notorious for their limitations. Ideally, one would not have to compromise design and code integrity for testability, but the reality is that the restrictions of popular testing frameworks such as jMock, Mockito, and EasyMock cause headaches for many developers. Does this look familiar?

private void methodToTest() { // something meaningful! } public void exposeMethodToTest() { methodToTest(); // i hate my life }

When researching these frameworks for testing a recent Day CQ5 project, I discovered JMockit and got (relatively) excited about it’s ability to easily test things like final classes and private methods without modifying existing code. Rather than rehash the excellent documentation on their site, I’ll use a couple examples to demonstrate using JMockit to simulate the interactions between the CQ5, Sling, and JCR APIs. 

@RunWith(JMockit.class)
public final class SampleTest extends JMockitTest {

    @Test
    public void sampleTest(@Mocked final SlingHttpServletRequest request)
        throws RepositoryException {
        new Expectations() {
            Resource resource;
            Node node;

            {
                request.getResource();
                returns(resource);

                resource.adaptTo(Node.class);
                returns(node);

                node.hasProperty("somePropertyName");
                returns(false);
            }
        };

        final Resource requestResource = request.getResource();
        final Node resourceNode = requestResource.adaptTo(Node.class);

        assertFalse(resourceNode.hasProperty("somePropertyName"));
    }
}

Using JMockit’s Expectations API, it is quite simple to mock the object behaviors required to complete the test without unnecessarily creating full mock objects by implementing the Node or Resource interfaces. The basic pattern here is to record an expected method invocation (e.g. request.getResource()) and follow that with returns(), which specifies the expected return value for that invocation. Let’s consider a more complex example — testing private methods of a final servlet class.

public final class FormServlet extends SlingAllMethodsServlet {

    private static final Set PARAMETERS;

    private static final Set REQUIRED_PARAM;

    static {
        PARAMETERS = new HashSet();
        PARAMETERS.add(NodePropertyConstants.FIRST_NAME);
        PARAMETERS.add(NodePropertyConstants.LAST_NAME);
        PARAMETERS.add(NodePropertyConstants.EMAIL);
        PARAMETERS.add(NodePropertyConstants.CONFIRM_EMAIL);
        PARAMETERS.add(NodePropertyConstants.PHONE);
        PARAMETERS.add(NodePropertyConstants.COMPANY);

        REQUIRED_PARAM = new HashSet();
        REQUIRED_PARAM.add(NodePropertyConstants.FIRST_NAME);
        REQUIRED_PARAM.add(NodePropertyConstants.LAST_NAME);
        REQUIRED_PARAM.add(NodePropertyConstants.EMAIL);
        REQUIRED_PARAM.add(NodePropertyConstants.CONFIRM_EMAIL);
    }

    // omitted public methods ...

    private Map populateValues(final SlingHttpServletRequest request,
        final Map validationErrors) {
        final Map values = new HashMap();

        final ComponentRequest componentRequest = new ComponentRequest(request);

        for (String parameterName : PARAMETERS) {
            values.put(parameterName, getValidatedParameter(componentRequest,
                parameterName, validationErrors));
        }

        return values;
    }

    private String getValidatedParameter(final ComponentRequest componentRequest,
        final String parameterName, final Map validationErrors) {
        final String value = componentRequest.getRequestParameter(parameterName);

        // omitted validation code ...

        return value;
    }
}

Here is the corresponding test:

@RunWith(JMockit.class)
public final class TestFormServlet extends JMockitTest {

    private FormServlet servlet;

    @Before
    public void setUp() {
        servlet = new FormServlet();
    }

    @Mocked
    ComponentRequest componentRequest;

    @Test
    public void testPopulateValues(@Mocked SlingHttpServletRequest request) {
        final Map errors = new HashMap();

        new NonStrictExpectations() {
            {
                componentRequest.getRequestParameter(anyString);
                returns ("");
            }
        };

        final Map values = Deencapsulation.invoke(servlet, "populateValues",
            request, errors);
        final Set parameters = Deencapsulation.getField(RSVPFormHandlerServlet.class,
            "REQUIRED_PARAM");
        final Set allParams = Deencapsulation.getField(RSVPFormHandlerServlet.class,
            "PARAMETERS");

        new Verifications() {
            {
                componentRequest.getRequestParameter(anyString);
                times = allParams.size();
            }
        };

        assertEquals(allParams.size(), values.size());
        assertFalse(errors.isEmpty());
        assertEquals(parameters.size(), errors.size());
    }
}

We instantiate our servlet, then use the anyString field in the NonStrictExpectations class to denote that we expect a call to componentRequest.getRequestParameter() with ANY string parameter to return an empty string. The Deencapulation utility allows us to directly access private variables and invoke the private method to be tested. We then use the Verifications block to ensure that componentRequest.getRequestParameter() was called for each parameter, using the times field. Our test is completed with standard JUnit assertions to ensure that the “replay” phase of the test produced the expected results.

The examples above are simplistic, but they illustrate the power and flexibility of JMockit to adapt itself to YOUR code, rather than the other way around. Good tools like JMockit give developers the freedom to write high quality, uncompromised code that functions independently of it’s associated unit tests.

Not quite ready to abandon your favorite testing framework for JMockit? Have a look at this comparison matrix: http://code.google.com/p/jmockit/wiki/MockingToolkitComparisonMatrix

Additionally, JMockit has provided a full set of sample test suites from other toolkits with corresponding JMockit versions (extremely helpful for migrating from EasyMock, Mockito, etc.):

http://jmockit.googlecode.com/svn/trunk/www/samples.html

I would encourage anyone writing Java unit tests to give JMockit a try — it takes very little effort to learn and incorporate it into an existing project, and you’ll soon be exercising coverage over previously “untestable” code.