Unit Testing in Adobe/Day/CQ5

There are a two approaches to effectively test your code in CQ5 – either outside of CQ5 using standard unit tests instantiated from your build tool (i.e. maven). While this approach will cover most simple functionality, the need will quickly arise to execute unit tests within CQ5/Sling/OSGi.

Testing outside of CQ5

In addition to your standard junit tests, you can grab a hold of the CRX repository and start writing some basic tests against that. As you can see in the following example, you ca even wrap the JCR-repository in its Sling equivalent:

package com.jtoee.cq.test;

import org.a~pache.jackrabbit.commons.JcrUtils;
import org.apache.sling.jcr.api.SlingRepository;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Repository;
import javax.jcr.RepositoryException;

public class SimpleTest {
    private Logger log = LoggerFactory.getLogger(SimpleTest.class);

    @Test
    public void runTest() {
        try {
            log.debug("Connecting to respository");
            Repository repository = JcrUtils.
                    getRepository("http://localhost:4502/crx/server");
            SlingRepository slingRepo =
                    new RepositoryUtil.RepositoryWrapper(repository);
            log.debug("Retrieved sling repository");
            // .. do something
        } catch (RepositoryException e) {
            log.debug("Exception retrieving repository.", e);
        }
    }
}

Testing within CQ5

If you’ve dug into CQ5 a little more, you’ve noticed the the power of the OSGi container and have probably started writing a lot of components (services) and wired them together via dependency injection. Luckily there’s a very easy way to run unit tests directly in the container and even use the OSGi-style dependency injection directly in your test, allowing you not only to write simple unit tests but also more involved automated integration tests.

According to the Apache Sling testing guideline there are a few approaches but I shall only focus on one of them today.

First, we will need to install a new Sling Junit OSGi bundle into Felix (http://localhost:4502/system/console). Grab it via subversion and compile it:

$ svn co http://svn.apache.org/repos/asf/sling/trunk/testing/junit/core

$ cd core

$ mvn package

Then go into the Felix console (Install/Update button on the right) and install the resulting jar file as a new component (it’s in core/target/org.apache.sling.junit.core-1.0.7-SNAPSHOT.jar):

Now you’re ready to write your first test into one of your custom OSGi bundles. One thing to keep in mind is that while with regular unit tests you would write your tests in the src/test/java directory structure (assuming a standard Maven project layout), you would put these tests into the src/main/java directory – you want them deployed and executed as part of the bundle.

Let’s take a look at a first test:

/**
 * <p/>
 * User: jochen
 * Date: 9/24/11
 * Time: 9:08 PM
 */
@RunWith(SlingAnnotationsTestRunner.class)
public class MyAwesomeTest {

    @TestReference
    private BundleContext bundleContext;

    @TestReference
    private SlingRepository repository;

    @TestReference
    private ResourceResolverFactory resourceResolverFactory;

    @TestReference
    private CloudStorageService storage;

    /**
     * Get a sling resource resolver
     */
    public ResourceResolver getResourceResolver() {
        try {
            return getResourceResolverFactory().
                    getAdministrativeResourceResolver(null);
        } catch (LoginException e) {
            fail(e.toString());
        }
        return null;
    }

    @Test
    public void testCreateFutureAttachment()
            throws Exception {
        assertTrue(true);
    }
}

As you can see with the class annotation @RunWith(SlingAnnotationsTestRunner.class) we’re telling the system to automatically inject OSGi-dependencies into the test case (you can’t use the regular @Reference annotations you might be using in the rest of your code since these have a compile-time retention policy). Use the @TestReference annotations to inject any of your defined components into your test. The rest of the test is standard Junit.

Maven Setup

Before running the tests, you need to register a regex for the test runner to identify your tests by name, such as the following Maven example does:

<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <extensions>true</extensions>
  <configuration>
    <instructions>
        <Export-Package>
            com.jtoee.cq.test.*
        </Export-Package>
        <Sling-Test-Regexp>.*Test</Sling-Test-Regexp>
        <Private-Package></Private-Package>
        <Import-Package>
   ...

Running Your Tests

The Sling unit testing core package installed earlier includes a simple servlet runner which is available via http://localhost:4502/system/sling/junit/ (adjust to your particular installation). You can simply run the tests from there. The runner implements various selectors, namely .html, .xml, and .json. Especially the XML-based format lends itself toward an easy integration into an automated build process:

$ curl -X POST http://localhost/system/sling/junit/com.jtoee.cq.xml

While I’ve come to love using the the JSON format via curl for readability, it doesn’t give you the stack traces in case any of your tests fail. However, the XML format does.

Conclusion

You have no more excuse not to write unit tests.

-->