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.

Comments

7 Responses to “Unit Testing in Adobe/Day/CQ5”

  1. Nitin on October 4th, 2011 6:14 am

    Thanks for this valuable post.
    I’m trying to combine yours with the post @ http://groups.google.com/group/day-communique/browse_thread/thread/4c0f25c56c3b6c02/cfaa2a035d74e5f3?lnk=gst&q=unit+test#cfaa2a035d74e5f3.
    I installed the org.apache.sling.junit.core bundle. However am not able to get the info page @ http://localhost:4502/system/console/sling/junit/, it goes to 404 and redirects to the bundles page.

    Also the components are not registered in felix. Did you actually change the @Components meta info in the files (e.g. JUnitServlet) to the @scr.component definition.

    As mentioned in the other forum i went ahead and installed the org.apache.sling.testing.tools, org.apache.sling.junit.scriptable and org.apache.sling.junit.remote.

    Thanks,
    Nitin

  2. Nitin on October 4th, 2011 6:15 am

    Sorry typo error, tried accessing the page @ http://localhost:4502/system/sling/junit/

  3. Jochen on October 5th, 2011 4:37 pm

    That’s strange. If you don’t have the /system/sling/junit — make sure you installed the Sling Junit core package and that it’s running.

    Our components are a mix of the comment-based @scr.component annotations and the actual @Component annotations. I hate the former since they remind me of xdoclet (which is probably what they are). But both work just fine. You don’t have to change any of the existing services.

    All you should need to do is have a bundle with tests which export test packages from the bundle — so if you’re using Maven, make sure the maven-bundle-plugin part needs to have the right Export statement. That should be enough for the junit bundle to see your tests.

  4. Bertrand Delacretaz on October 10th, 2011 3:12 am

    I have to say that I love your conclusion ;-)

  5. Chris Hunter on October 27th, 2011 4:39 pm

    I’m having a hard time following your example. I’m not able to resolve RepositoryUtil object.

    I’m certainly no CQ5 expert, but I need to setup some tests where I mock the slingServlet and currentPage objects. Is this the best way to do this?

    Can you provide maven dependencies and possibly elaborate further? This is a great post though, thanks for putting time into it.

    Chris

  6. Sander van Beek on October 31st, 2011 9:44 am

    Hi Jochen, thanks for this interesting blogpost! I’ve tried to create a setup similiar to yours and get the test runner to see my tests. But when I try to run them I get the following exception:

    TEST FAILED: initializationError(nl.tricode.my.package.ExampleTest)
    initializationError(nl.tricode.my.package.ExampleTest): No runnable methods

    The method however is public and has a @Test annotation. Even the simple example below won’t run:

    @RunWith(SlingAnnotationsTestRunner.class)
    public class ExampleTest {

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

    }

    Any ideas?
    Cheers,
    Sander

  7. Shawn on January 16th, 2012 12:55 pm

    I am trying to follow this to test within CQ5, but when I try to compile my unit tests within CRXDE, the import statements fail because they “cannot be resolved.” What does it take for CRXDE to “see” the namespaces that are exported when the JUnit OSGI bundle is installed in Felix? For example, I have installed and activated the bundle, but this import statement won’t compile in my CRXDE instance that is pointing to the CQ5 instance where I installed the bundle: import org.junit.*;

Leave a Reply




XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

-->