Activiti is designed to be integrated with your high level business process. There are many ways to integrate with Activiti such as using a Camel or Mule Task to interact with running workflows or start a new process. Activiti also supports an extensive REST interface to allow applications to integrate directly or through an ESB with a workflow. During code development any integration points should be tested in context of the overall Activiti system. Ideally developers should have the ability to run integration tests prior to any code releases and should include automated integration tests to be run within a Continuous Integration environment. This blog shows how to integration test an Activiti Rest interface. It uses Docker containers to launch an Activiti (APS 1.9) system, include a custom Rest interface into the activiti-engine application, start the activit-app webapp and then test that calling the custom Rest request returns the expected set of results.
The following steps were undertaken to create this integration Test:
- Install Docker and Docker Compose
- Configure the maven project to support integration testing using the failsafe maven plugin.
- Create a Dockerfile to create a Docker image of the activity-app which contains our solution deliverables.
- Create a DockerCompose file to launch the Activiti (APS) environment
- Create a custom REST service that will be exercised as part of out test.
- Run the integration tests and verify the results.
Docker and Docker Compose
Docker allows us to use containers to run Activiti and its supporting software (eg Postgres DB). Instructions on how you can install Docker in a Ubuntu environment can be found at https://docs.docker.com/install/linux/docker-ce/ubuntu/.
Docker Compose is a tool for defining and running multi-container Docker applications. Alfresco has released a Docker Compose yaml file which will create and deploy all the containers required to run a full activity system. Instructions on installing Docker Compose can be found at https://docs.docker.com/compose/install/
Configuring the Maven Project to support Integration Tests
This blog assumes you have already set up your maven project for unit testing as specified in the previous blog https://www.seedbpm.com/1128/activiti-6-0-unit-testing/.
The failsafe maven plugin will be used to segregate integration tests from unit tests and so that we can use a maven goal to specifically run Integration tests through maven. Generally Integration tests take longer to run than Unit Tests as they require the setup and teardown of the complete environment you are testing against before running the test. Therefore, by segregating the Integration and Unit tests we can speed up the development cycle by only running Integration tests when required.
We also need to add the dependencies required to support calling a REST service from the Maven Project and testing the outcome.
Change your project POM to include the following:
- In the Repositories section add the Maven Main Repository:
<repository> <id>central</id> <name>Central Repository</name> <url>http://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository>
2. Add a profile for failsafe to your maven project so that Integration Tests can be run separately to the main build process.
<profiles> <profile> <id>failsafe</id> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.0</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
3. Add the required dependencies to support REST requests, Test checks (Hamcrest) and also to allow us to separate integration tests from unit tests in our project structure (Test Containers):
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.hamcrest/hamcrest --> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.11.0</version> </dependency>
4. Create an integration test source folder in your maven project and configure the project to look for Integration Tests in this location.
Create the following folder structure: <Project-Root>/src/integration-test/java. All Integration Tests will be created under this folder.
Add the following to your Plugins section of your POM. As you can see the Source tag refererences the folder structure just created.:
<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>
5. Rebuild your project by calling mvn package.
Creating a Docker Release and Docker Compose file to lauch Activiti
My Integration test will launch a Docker image of the activiti-app webapp which should include any source code and configuration that I have created for the solution. I therefore need to build a new docker image. To do this I have used the APS published Docker Image as a starting image and then added my updates to it. Docker Images are built from Dockerfiles. I have therefore included a Dockerfile into my Maven Project with instructions on how to build the Docker Image. The solution code will be packaged up into a JAR as part of the build and the Jar will be copied across into the Docker image I am creating into the tomcat/webapps/activity-app/WEB-INF/lib folder. This means that when the Docker Image is loaded, following build, it will include all of my customisations and therefore they will be available for me integration test against.
1. Create the following folders in your maven project
<project-root>/docker/process-app
<project-root>/docker/process-app/modules/jars
2. Add a file called Dockerfile to the folder <project-root>/docker/process-app. The Dockerfile uses Alfresco Process Services 1.9.0.1 as its base image, copies the project release Jar onto the image, unzips the base images activity-app war into tomcat and copies the release Jar into the unpacked webapp lib directory.
Add the following to the Dockerfile:
FROM alfresco/process-services:1.9.0.1 ARG TOMCAT_DIR=/usr/local/tomcat COPY modules/jars/ /usr/local/jars RUN set -x \ && mkdir /usr/local/tomcat/webapps/activiti-app \ && unzip /usr/local/tomcat/webapps/activiti-app.war -d /usr/local/tomcat/webapps/activiti-app \ && mv /usr/local/jars/* $TOMCAT_DIR/webapps/activiti-app/WEB-INF/lib/.
3. The next step is to create a Docker-Compose yml file that will launch the activity-app container built using the Dockerfile above and also launch a PostgreSQL db container to support it. For this we create a docker-compose.yml file in <project-root>/docker folder. You also need to include the activity license file in this folder which you can see if referenced in the docker-compose file. This is necessary as we are running the APS image and not activiti community. The docker-compose.yml file has the following content. Note that the process app is building a new image on launch based on the Dockerfile we created earlier in the sub folder process-app.
version: '2' services: process: build: ./process-app environment: ACTIVITI_DATASOURCE_USERNAME: alfresco ACTIVITI_DATASOURCE_PASSWORD: alfresco ACTIVITI_DATASOURCE_DRIVER: org.postgresql.Driver ACTIVITI_HIBERNATE_DIALECT: org.hibernate.dialect.PostgreSQLDialect ACTIVITI_DATASOURCE_URL: 'jdbc:postgresql://postgres:5432/activiti?characterEncoding=UTF-8' ACTIVITI_CSRF_DISABLED: 'true' ACTIVITI_CORS_ENABLED: 'true' volumes: - "./:/root/.activiti/enterprise-license/:ro" - ./psdata:/usr/local/data/ ports: - 9999:8080 links: - postgres:postgres depends_on: - postgres postgres: image: postgres:9.6.2 environment: POSTGRES_DB: activiti POSTGRES_USER: alfresco POSTGRES_PASSWORD: alfresco volumes: - ./pgdata:/var/lib/postgresql/data
Create Custom Rest Service
The Rest service is the target of our integration test. Activiti supports creation of custom REST services to allow for business specific use cases. Documentation on creating a custom REST service can be found at http://docs.alfresco.com/process-services1.9/topics/rest_api.html
For this test we have created a very simple REST service that gets all running tasks for a user and responds with JSON giving the users full name and number of tasks assigned. We will test these response parameters when we do our unit test.
1. Create the MyRestEndpoint.java class in your project source directory:
package com.activiti.extension.api; import com.activiti.domain.idm.User; import com.activiti.extension.bean.DynamicWfService; import com.activiti.extension.bean.MyFirstSpringBean; import com.activiti.security.SecurityUtils; import org.activiti.engine.TaskService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/enterprise/my-rest-endpoint") public class MyRestEndpoint { @Autowired private TaskService taskService; private MyRestEndpointResponseData data = new MyRestEndpointResponseData(); @RequestMapping(method = RequestMethod.GET, produces = "application/json") public MyRestEndpointResponseData executeCustonLogic() { System.out.println("****************** IN REST REQUEST *******************************"); User currentUser = SecurityUtils.getCurrentUserObject(); long taskCount = taskService.createTaskQuery().taskAssignee(String.valueOf(currentUser.getId())).count(); ?//MyRestEndpointResponse myRestEndpointResponse = new MyRestEndpointResponse(); data.setFullName(currentUser.getFullName()); data.setTaskCount(taskCount); return data; } }
Create Integration Test class
Now that everything is set up we can write our Integration Test Class. When we run the maven failsafe test profile it will run any test classes it finds that end with “IT”. To create our test class:
- Create a test class called MyFirstRestIT.java in the <project-root>/src/test/java/<package> folder with the following content:
package com.activiti.extension.api;
import static org.junit.Assert.assertThat;
import java.io.File;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.HttpClientBuilder;
import org.hamcrest.Matchers;
import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.DockerComposeContainer;
public class MyFirstResIT {
@ClassRule
public static DockerComposeContainer compose =
new DockerComposeContainer(
new File(“docker/docker-compose.yml”))
.withExposedService(“process_1”, 8080);
@Test
public void givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse()
throws Exception {
String address = “http://admin%40app.activiti.com:admin@”
+ compose.getServiceHost(“process_1”, 8080) + “:”
+ compose.getServicePort(“process_1”, 8080) +
“/activiti-app/api/enterprise/my-rest-endpoint”;
HttpUriRequest request = new HttpGet(address);
HttpResponse response = HttpClientBuilder.create().build().execute( request );
MyRestEndpointResponseData resource = RetrieveUtil.retrieveResourceFromResponse(
response, MyRestEndpointResponseData.class);
assertThat( ” Administrator”, Matchers.is( resource.getFullName() ) );
}
}
This file uses an @ClassRule decorator to start the activity environment using the docker-compose file created earlier.
Any methods decorated with @Test are then run. As you can see we created a test method called givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse which uses a http request to call the REST endpoint we configured in our custom REST service:
String address = “http://admin%40app.activiti.com:admin@”
+ compose.getServiceHost(“process_1”, 8080) + “:”
+ compose.getServicePort(“process_1”, 8080) +
“/activiti-app/api/enterprise/my-rest-endpoint”;
HttpUriRequest request = new HttpGet(address);
Once the method has been called we use a hamster matcher to test the value in the response is as we expected. We are doing a very simple test as this blog is meant to show you how to do a test.
assertThat( ” Administrator”, Matchers.is( resource.getFullName() ) );
Running the Integration Test
To run the integration test go to the root of your maven project and execute the failsafe test profile as follows:
mvn verify -Pfailsafe -DskipSurefire=true
This will launch activiti using docker, run your Integration Test and then tear down the environment again. Reporting on each test can be found under target/failsafe. Note, if you want to run both Unit Tests and Integration Tests you can run the above command with a value of false for skipSurefile.
Hopefully this will help you set up and run integration tests in your environment and avail of Docker to manage your build and test cycle in an efficient manner.