Writing Mavenized JUnit tests in Alfresco.

Writing JUnit tests in Alfresco projects using Maven is not very straightforward. The main problem is lack of a “parent POM” which would gather all Alfresco dependencies into one convenient package. I will show you how to create such parent POM as well as how to create a simple JUnit test project.

Prerequisites are:

  • Alfresco (I have used 3.3g)
  • Python
  • Maven (of course :))

We will use dependencies from Alfresco WAR file, perhaps the right way is to get them from Alfresco SDK, but since I haven’t experienced any problems with using JARs from alfresco.war and since it is also easier to build pom.xml from alfresco.war then this is what I recommend.

So, to create our parent pom.xml with all Alfresco dependencies we will need a small script:


import os
import sys

files = os.listdir(sys.argv[1])

groupId="alfresco-sdk"
version="3.3g"
classifier="community"

print """
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.metasys</groupId>
<artifactId>alfresco-sdk-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>alfresco-sdk-parent</name>
<dependencies>
"""

for file in files:
artifactId = file[:-4]
c = "mvn install:install-file -Dfile=" + os.path.join(sys.argv[1], file) + \
       " -DgroupId=" + groupId + \
       " -DartifactId=" + artifactId + \
       " -Dversion=" + version + \
       " -Dpackaging=jar -Dclassifier=" + classifier + \
       " -DgeneratePom=true -DcreateChecksum=true"
s = "\t<dependency>\n\t<scope>test</scope>\n\t<groupId>" + groupId + \
       "</groupId>\n\t<artifactId>"+ artifactId + \
       "</artifactId>\n\t<version>"+ version + \
       "</version>\n\t<classifier>community</classifier>\n\t</dependency>\n"
print s
if len(sys.argv) > 2 and sys.argv[2] == 'install':
      os.system(c)

print """
</dependencies>
</project>
"""

There are some hardcoded values in the script. Change them if you want, for example if you’re working with Alfresco 3.4 then change the version variable.
We will invoke it like this:


mkdir alfresco-parent-pom
cd alfresco-parent-pom
python alfresco-2-maven.py <Path to Alfresco's WEB-INF/lib folder> >pom.xml

Now, move the pom.xml to a newly created folder and install it using:


mvn install

At this moment we have a Maven project ready to use but we still don’t have dependencies in the Maven repository. To install them start the script again, this time with ‘install’ parameter:


python alfresco-2-maven.py <Path to Alfresco's WEB-INF/lib folder> install

This will take some time to finish but eventually you will end up with a local repository with all Alfresco dependencies.

No, let’s move on to a real project with some JUnit tests. Our test will have to start an Alfresco repository, each Alfresco repository apart from database and filestore needs configuration. You can find Alfresco configuration in alfresco/WEB-INF/classes/alfresco folder. In order to start the tests we will need that folder in our CLASSPATH or create a JAR file with configuration files. I prefer the second solution. So to prepare the configuration artifact follow these steps:


cd Alfresco/tomcat/webapps/alfresco/WEB-INF/classes
zip -r /tmp/config.jar .
mvn install:install-file -Dfile=config.jar -DgroupId=alfresco-sdk -DartifactId=config -Dversion=3.3g -Dpackaging=jar -Dclassifier=community -DgeneratePom=true -DcreateChecksum=true

You can cleanup the config.jar before installing it in the local Maven repository. If you want to have multiple configurations then just modify the classifier parameter or simply use different version modifier.

Now, finally, we can move on to our test class. We start with the pom.xml.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.metasys</groupId>
    <artifactId>test-suite</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>test-suite</name>

    <parent>
        <groupId>com.metasys</groupId>
        <artifactId>alfresco-sdk-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/test/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <scope>test</scope>
            <groupId>alfresco-sdk</groupId>
            <artifactId>config</artifactId>
            <version>3.3g</version>
        </dependency>
  <dependency>
            <groupId>javax</groupId>
            <artifactId>servlet</artifactId>
            <version>1.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

As you can see apart from referencing parent POM I have also added Mysql JDBC driver and servlet-api as dependencies. These two jars are present in Alfresco SDK but not in alfresco.war and that’s why they were not picked up the Python script. Of course there is also the Alfresco Configuration dependency we created before.

Now, since we have the pom.xml let’s move on to creating the Java class, we will put it in a package named com.metasys.tests, so let’s create the folder structure first:


mkdir -p JUnitTest/src/test/java/com/metasys/tests
mkdir -p JUnitTest/src/test/resources/alfresco/extension
mkdir -p JUnitTest/src/test/resources/alfresco/desktop

Change folder to src/test/java/com/metasys/tests and create a new Java class:


package com.metasys.tests;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.util.BaseAlfrescoTestCase;
import org.junit.Test;

public class SimpleTest extends BaseAlfrescoTestCase {

    protected NodeRef companyHomeRef;
    protected NodeRef rootFolderTestRef;

    @Override
    protected void setUp() throws Exception {
        setUpContext();

        this.serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
        this.nodeService = serviceRegistry.getNodeService();
        this.contentService = serviceRegistry.getContentService();
        this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
        this.actionService = (ActionService) ctx.getBean("actionService");
        this.transactionService = serviceRegistry.getTransactionService();

        authenticationComponent.setCurrentUser("admin");
        SearchService searchService = this.serviceRegistry.getSearchService();
        ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH,
                "/app:company_home");
        if (rs.length() != 1) {
            fail("Could not find company home");
        }
        companyHomeRef = rs.getNodeRef(0);
    }

    @Test
    public void testSimpleWatermarking() throws Throwable {
        rootFolderTestRef = serviceRegistry.getNodeService().createNode(
                companyHomeRef, ContentModel.ASSOC_CONTAINS,
                ContentModel.TYPE_FOLDER, ContentModel.TYPE_FOLDER).getChildRef();
  assert(rootFolderTestRef != null);
        nodeService.setProperty(rootFolderTestRef, ContentModel.PROP_NAME, "TestObject");
  assert(nodeService.getProperty(rootFolderTestRef, ContentModel.PROP_NAME).equals("TestObject"));
    }
}

We will also need bootstrap configuration, normally these files are somewhere in Tomcat folder tree, since we’re not using Tomcat for starting up the repository we will have to find a place for them.

First file is dev-contex.xml, it has to be saved in src/test/resources/alfresco/extension:


<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>
    <bean id="global-properties" class="org.alfresco.config.JndiPropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:alfresco/repository.properties</value>
                <value>classpath:alfresco/domain/transaction.properties</value>
  <!-- <value>classpath:alfresco/jndi.properties</value> -->
  <!--  Overrides supplied by modules -->
                <value>classpath*:alfresco/module/*/alfresco-global.properties</value>
  <!--  Installer or user-provided defaults -->
                <value>classpath*:alfresco-global.properties</value>
                <value>classpath:alfresco/extension/dev.properties</value>
            </list>
        </property>
        <property name="systemPropertiesModeName">
            <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
        </property>
<!-- Extra properties that have no defaults that we allow to be defined through JNDI or System properties -->
        <property name="systemProperties">
            <list>
                <value>hibernate.dialect</value>
                <value>hibernate.query.substitutions</value>
                <value>hibernate.jdbc.use_get_generated_keys</value>
                <value>hibernate.default_schema</value>
            </list>
        </property>
    </bean>
</beans>

Next one is dev.properties, save it to the same folder as dev.properties:


dir.root=/home/kbryd/AlfrescoT2/alf_data
index.recovery.mode=AUTO
integrity.failOnError=true
db.name=alfrescoT2
db.username=alfrescoT2
db.password=alfrescoT2
db.host=localhost
db.port=3306
db.driver=org.gjt.mm.mysql.Driver
db.url=jdbc:mysql://${db.host}:${db.port}/${db.name}
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect

Obviously change the database name, username, password and path to the Alfresco filestore. The last (really!) missing bit is a package of Alfresco desktop files required during startup, just copy them from alfresco/WEB-INF/classes/alfresco/desktop to your project’s src/test/resources/alfresco/desktop folder.

Now, go to the root folder of the test project and use Maven to start it:


mvn test

It will start up the repository and then execute tests, you should see something like this:


Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 39.947 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: /tmp/test-project/target/test-suite-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 44 seconds
[INFO] Finished at: Sat Feb 26 14:45:26 CET 2011
[INFO] Final Memory: 30M/258M
[INFO] ------------------------------------------------------------------------

One ending note. You may wonder why I am not calling super.setUp() in the Java class. The only reason I am doing it is because the implementation of setUp() creates a separate Store for each test. I don’t like this default behavior and that’s why I am calling only setUpContext() and then creating all required beans in my test class.

I hope that this is useful, please email me or leave comments if you have any questions!