Automated Testing with Alfresco

by Bobby Johnson

In my time working on Alfresco projects, I’ve ended up writing quite a few policies and custom actions, as well as other Java components. The typical way to test these components out is to deploy your code into a local application server, start Alfresco, try things out manually, and then rinse and repeat until your code is working as expected. If you’ve implemented a custom repository action, you may have seen the wiki page that mentions testing actions, which provides a code example for a simple test extending a class called BaseSpringTest.[i] The section doesn’t provide quite enough information to get started, however it is possible to write Java tests for your Alfresco components that run in the context of a live Alfresco repository. I’ll give some details on how to get started and the advantages and disadvantages of automated testing inside an Alfresco repository.

The wiki code for testing custom actions provides a very basic example of what can be done when writing tests using this method. Here are a few of the things you can do once within this testing context:

  • Access any Spring bean in the application context, including your own.
  • Manipulate the Alfresco repository any way you can within a custom action or other Java component, including creating and updating nodes, executing other actions, etc.
  • Make assertions on the state of the repository after executing code.

This is all possible because Alfresco uses the Spring Framework. The entire Spring application context for Alfresco can be initialized outside of a web application, and that is what the base test class org.alfresco.util.BaseSpringTest helps us to do. If you have looked through the Java sources included in the Alfresco SDK, you’ve seen that Alfresco has a large number of test classes extending this class for their own testing. Their test classes also provide useful examples of setting up the repository state to test more complicated scenarios.

Everything you need to run Spring context JUnit tests is actually included in the SDK. You’ll probably want to use Apache Ant or another build tool to get your testing classpath setup appropriately, specifically including:

  • The Alfresco JARs under lib/server, and all dependencies under lib/server/dependencies
  • Alfresco’s default Spring context and other configuration files (models, database scripts, etc.) This is found in lib/server/config.
  • Your custom Spring context and all the code you want to test, including any custom models, workflows, etc.
  • Extra Alfresco configuration, including alfresco-global.properties and log4j.properties to override the defaults that come with the SDK.

Here are snippets of an appropriate Ant classpath and JUnit task configuration to get you started:

<!-- the alfresco SDK JARs. -->
    <path id="classpath.alfresco.sdk">
        <fileset dir="${alfresco.sdk.dir}/lib/server">
            <include name="**/*.jar" />
            <exclude name="**/ant-1.7.1.jar" />
            <!-- 4.0.0 sdk seems to have duplicate files under server/bin. -->
            <exclude name="bin/*" />
        </fileset>
    </path>

    <!-- we test against the SDK, plus some other config and our source code, of course. -->
    <path id="test.classpath.path">
        <fileset dir="lib">
            <include name="*.jar" />
        </fileset>

        <!-- we want the classpath for config file to be alfresco/..., not config/alfresco, so we need a separate path element. -->
        <pathelement location="${alfresco.sdk.dir}/lib/server/config" />

        <!-- tests depend on our non-test code. -->
        <pathelement location="${project.dir}/classes" />
    </path>

<!-- The Alfresco app context has the same requirements for heap and PermGen size that it does when run in an app server. -->
        <junit printsummary="yes" haltonfailure="false" fork="yes" showoutput="yes" maxmemory="768m">
            <jvmarg value="-server" />
            <jvmarg value="-XX:MaxPermSize=256M" />
            <jvmarg value="-Dcom.sun.management.jmxremote" />

            <classpath>
                <pathelement location="${test-classes.dir}" />
                <path refid="classpath.alfresco.sdk" />
                <path refid="test.classpath.path" />
            </classpath>
            <formatter type="xml" />

            <batchtest fork="yes" todir="${test-reports.dir}">
                <fileset dir="${test.dir}">
                    <include name="**/Test*.java" />
                </fileset>
            </batchtest>

        </junit>

Because running your test will start up a full Alfresco application context, you need to provide Alfresco with a real database and file system location to hold the content store and indexes, just like running the Alfresco web application. Using an embedded database like hsqldb seems to be problematic[ii], so I ended up using a new database on the same MySQL server I run Alfresco with locally. Once you run your test with the classpath setup correctly, Alfresco will startup, and for each of your test methods, JUnit will call onSetUpInTransaction() and then run the test method. Note that each of your tests run in a new rollback transaction by default, so your changes to the repository won’t persist across tests.

Purely as an example, here is the basic structure of what my test classes look like (package and imports omitted for brevity):

public class TestExampleSpringTest extends BaseSpringTest {
    private AuthenticationComponent authenticationComponent;
    private NodeService nodeService;
    private Repository repositoryHelper;

    @SuppressWarnings("deprecation")
    @Override
    protected void onSetUpInTransaction() throws Exception {
        super.onSetUpInTransaction();

        // grab any spring beans we need. You may wish to test against protected
        // services (e.g. 'NodeService')
        this.nodeService = (NodeService) applicationContext.getBean("dbNodeService");
        this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent");
        this.repositoryHelper = (Repository) applicationContext.getBean("repositoryHelper");

        // Set the current authentication to the admin user.
        // this is the easiest way to get an authenticated for manipulating the
        // repository.
        this.authenticationComponent.setCurrentUser("admin");
    }

    public void testExampleTest() {
        // normally you'd test your own custom code here.
        NodeRef companyHome = repositoryHelper.getCompanyHome();
        String fileName = "myFile.txt";
        QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, fileName);
        Map fileProperties = new HashMap();
        fileProperties.put(ContentModel.PROP_NAME, fileName);
        ChildAssociationRef newNodeAssoc = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, assocQName,
                ContentModel.TYPE_CONTENT, fileProperties);

        NodeRef newFileNodeRef = newNodeAssoc.getChildRef();
        assertNotNull(newFileNodeRef);
        assertEquals("file name should be correct", fileName,
                nodeService.getProperty(newFileNodeRef, ContentModel.PROP_NAME));
    }

}

As a big fan of automated testing, I find the ability to test my code within the context of a real Alfresco repository invaluable. At Rothbury Software, we use Jenkins for continuous integration, and being able to run these robust tests of our code as part of our build process has many advantages. There is a cost in terms of test run times, as each test class needs to start up a fresh Alfresco context. However, the ability to continually validate functionality enables things such as refactoring without fearing regressions. This could also be advantageous if you want to use a Test Driven Development process for Alfresco development. I hope you’ve found this useful if you’ve ever been curious about the example test for custom actions, or wanted the ability to fully exercise your custom Alfresco code as part of a Continuous Integration process.


[i] http://wiki.alfresco.com/wiki/Custom_Actions#Testing_the_action

[ii] https://issues.alfresco.com/jira/browse/ALFCOM-3691