Unit testing is an important part of the development cycle when creating a new processes in Activiti. Unit tests can be used to test a full workflow from Start Task to End Task or for testing individual classes or beans that are used within your workflow for items such as service tasks, listeners etc. Alfresco also encourage you to write a unit test when submitting a case for any issues you would like them to look at.
To unit test a service task we will follow the following steps:
- Configure our Maven Project to support unit testing Activiti.
- Install the Surefire Plugin to run and report on unit tests.
- Create the Java Delegate class we are testing.
- Create a skeleton workflow definition that includes a service task that uses the JavaDelegate class above.
- Write a Unit Test that loads the workflow definition and steps through the workflow until it runs the Service task. Following the service task the unit test will validate the assumptions we are expecting.
- Run the Unit Test via mvn and view the results.
Configuring your Maven Project
This blog assumes you are testing a JavaDelegate class written in java and that you are building and testing your code using a mvn project. The mvn project should be configured with dependencies for Junit, the activiti-engine, activiti-spring and the H2 database. To get started add the following dependencies to your maven project POM.
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.168</version>
<scope>test</scope>
</dependency>
</dependencies>
Installing the Surefire Plugin to support Unit Testing
We will use the Surefire Plugin to run our unit test. The Surefire Plugin is used during the test phase of the mvn build lifecycle to execute the unit tests of an application. It generates reports in xml and text format that can be read manually for failed tests or from a Continuous Integration environment such as Jenkins. The plugin should be added to your POM as follows.
<build>
<plugins>
<!-- test config -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skip>${skipSurefire}</skip>
<systemProperties>
<property>
<name>tempDir</name>
<value>${project.build.directory}</value>
</property>
<property>
<name>baseDir</name>
<value>${project.basedir}</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-integration-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/integration-test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
The Surefire Plugin can be invoked by calling the test phase of the build lifecycle. This will run junit tests for all classes that end in Test.
Create a skeleton Process definition to include a Service Task
For our testing we have created a skeleton Process Definition to include a service task that calls the JavaDelegate we are testing. You could also test by downloading an already created process definition that includes the service task being tested. Processes are often developed using the BPMN Designed in activiti and then downloaded as xml so that they can be deployed for the test.
The process definition xml file that includes your service task should be stored in the Resources directory of your maven project, ie in our case for this test in /src/test/resources/org/activiti/test/GetClientDetailsProcess.bpmn20.xml.
Our process is fairly simple, it merely has a start task, a service task called GetClientDetailsServiceTask which then flows onto the user task userTask1 before flowing to the end task. We include a user task so that we can test our assumptions after the service task has run but before the workflow has completed. This could also have been achieved by including a triggerable flag on the service task which would mean it would run when the process is started and then stop waiting for a trigger before moving onto the next task, or workflow end. The contents of the skeleton process definition file is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="GetClientDetailsProcess">
<startEvent id="start" />
<sequenceFlow id="toServiceTask1" sourceRef="start" targetRef="serviceTask1" />
<serviceTask id="serviceTask1" name="GetClientDetailsServiceTask" activiti:class="com.activiti.seed.GetClientDetailsJavaDelegate" />
<sequenceFlow id="toUserTask1" sourceRef="serviceTask1" targetRef="userTask1" />
<userTask id="userTask1" name="userTask1" />
<sequenceFlow id="toEnd" sourceRef="userTask1" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>
Create the Service Java Delegate
Create the following file in your project in the directory/package /src/java/com.activiti/seed. package com.activiti.seed; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.JavaDelegate; public class GetClientDetailsJavaDelegate implements JavaDelegate { private String clientName = "SeedBPM Client"; /** * Delegate example method to run ServiceTask logic. * * set the fixed value of clientName SeedBPM Client as a process variable. * */ public void execute(DelegateExecution execution) throws Exception { System.out.println("GetClientDetailsJavaDelegate Entered"); // Make an external call to get the users details // getClientDetails(); // set the value into the process as a variable called clientName. execution.setVariable("clientName", this.clientName); System.out.println("GetClientDetailsJavaDelegate Exited"); } }
Junit Test Class
The next step is to create a Junit test class. The class will create and load a full activiti engine prior to running the test methods in the class. The sample Junit test class is shown below:
package au.com.seedim.java; import org.activiti.engine.TaskService; //import org.activiti.engine.impl.util.ProcessDefinitionUtil; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.activiti.engine.test.ActivitiRule; import org.activiti.engine.test.Deployment; import org.junit.Rule; import org.junit.Test; import org.activiti.bpmn.model.FlowElement; import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.bpmn.model.UserTask; import static org.junit.Assert.*; import java.util.Map; public class GetClientDetailsServiceTaskUnitTest { public int counter = 0; @Rule // 1 public ActivitiRule activitiRule = new ActivitiRule(); @Test // 2 @Deployment(resources = {"org/activiti/test/GetClientDetailsProcess.bpmn20.xml"}) // 3 public void test_oncalltoGetClientDetailsServiceTask_clientNameIsSet() { ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("ServiceTaskProcess"); assertNotNull(processInstance); Task task = activitiRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId()).includeProcessVariables().singleResult(); assertEquals("userTask1", task.getName()); // check that the variable clientName was set when it gets to this task. The SErvice task comes before this so // the clientName variable should be set to SeedBPM Client Map<String, Object> processVariables = task.getProcessVariables(); assertEquals(processVariables.get("clientName"), "SeedBPM Client"); } }
Explaining the Junit Class
@Rule
The important things to consider with this class are highlighted in Bold. The first of these is the @Rule which injects the ActivitiRule class into the junit test. This manages the setup and teardown of a full activiti engine and provides a reference to the engine context so that you can get access to the public services such as the RepositoryService and TaskService.
@Rule // 1
public ActivitiRule activitiRule = new ActivitiRule();
@Test
The next import decorator is @Test. It is used by Junit to indicate that this method is running a test.
@Deployment
The third important decorator is the @Deployment decorator.
@Deployment(resources = {“org/activiti/test/GetClientDetailsProcess.bpmn20.xml”}) // 3
This points at the Process model you want to deploy into the activiti engine before running your test. As you can see it points at the GetClientDetailsProcess.bpmn20.xml process definition we created earlier.
Testing our Assumptions
We have written this JUnit to test that the service task java delegate, when called, will get and set the client name into the process variable clientName. The JavaDelegate class is shown below. As you can see it implements JavaDelegate and therefore the work is carried out in the execution method. This method merely sets “SeedBPM Client” into the clientName process variable. ie
execution.setVariable(“clientName”, this.clientName);
If you take a look back at the GetClientDetailsProcess.bpmn20.xml model we deployed as part of the Junit test we are writing you can see that the Service Task references this class as its java delegate class.
Run the JUnit Test
The Junit test can be run by calling mvn test at the root of your project. Navigate to the root of your maven project and execute the following command.
mvn test
This will run all junit tests that you have defined. The results of each test will be stored in your project under /target/surefire. For any failed tests you can view the cause and navigate to the failed test in your test class.