Motivation
Testing your processes is an important tasks to ensure and validate your expected behaviour of your application. An introduction how to do a proper test automation in process applications can be found the following camunda webinar: https://network.camunda.org/webinars/24
A normal approach for testing your processes is to have your actual service implementation mocked or swapped completely to your own implementation for testing purposes. For CDI based java delegates this is an easy task to do within the camunda BPM test environment.
But if your project does not allow you to rely on your favourite CDI or Spring based environment you have to configure your java delegates for service tasks via class name binding. Unfortunately there seems not to be an out of the box approach to test that kind of configuration easily.
Will will show you how to get use of the great extensibility of the camunda BPM engine to have plain java delegates mocked as easy as their CDI/Spring counterparts.
Testing of EL based service tasks
This example shows how to test your normal CDI based service tasks fully supported out of the box in camunda BPM:
@Test @Deployment(resources = "process.bpmn") public void testRunningProcess() throws Exception { // registering a different service task implementation under the el name "fancyService" as used in the process definition Mocks.register("fancyService", new MockedFancyService()); ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(PROCESS_DEFINITION_KEY); ... }
What do we want to achieve is to be able to use that pattern for plain java delegates as well:
@Test @Deployment(resources = "process.bpmn") public void testRunningProcess() throws Exception { // Registering a different service task implementation under its classname as used in the process definition // BEWARE This is not working out of the box! Mocks.register("de.akquinet.camunda.FancyService", new MockedFancyService()); ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(PROCESS_DEFINITION_KEY); ... }
Unfortunately this will not work out of the box.
Camunda BPM under the hood
For having the mock feature for unit tests work at all there is some special configuration for camunda to be done.
In your typical camunda BPM project you will find the test configuration in your test source folder:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="processEngineConfiguration" class="org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration"> <property name="expressionManager"> <bean class="org.camunda.bpm.engine.test.mock.MockExpressionManager"/> </property> <property name="processEnginePlugins"> <list> <bean class="org.camunda.spin.plugin.impl.SpinProcessEnginePlugin" /> </list> </property> </bean> </beans>
The MockExpressionManager is needed for enabling mock stuff for testing at all. Since we are using a special ProcessEngineConfiguration anyway for unit tests, we can extend that further to have that mock feature work for plain java delegates too.
The camunda component responsible for creating a new instance of the actual java code behind a service task is:
org.camunda.bpm.engine.ArtifactFactory
Currently there are two implementations of this component, one for CDI environments and one for the plain java delegates:
org.camunda.bpm.engine.cdi.CdiArtifactFactory org.camunda.bpm.engine.impl.DefaultArtifactFactory
The default one is just using reflection to create a new instance of a simple java delegate class.
Using that kind of extension point we provide a third factory using the already used Mock class for registering mocks in the unit test case as shown above:
package de.akquinet.camunda; import org.camunda.bpm.engine.impl.DefaultArtifactFactory; import org.camunda.bpm.engine.test.mock.Mocks; public class MockArtifactFactory extends DefaultArtifactFactory { @Override public T getArtifact(Class clazz) { T mockedDelegate = (T) Mocks.get(clazz.getCanonicalName()); if (mockedDelegate == null) { return super.getArtifact(clazz); } return mockedDelegate; } }
The main idea is to check first if a mock is registered under the class name and if not the default implementation used. Now you can have mocked and real java delegates in your tests in any combination.
The next step is to register this new factory in the ProcessEngineConfiguration:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="processEngineConfiguration" class="org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration"> <property name="expressionManager"> <bean class="org.camunda.bpm.engine.test.mock.MockExpressionManager"/> </property> <property name="artifactFactory"> <bean class="de.akquinet.camunda.MockArtifactFactory"/> </property> <property name="processEnginePlugins"> <list> <bean class="org.camunda.spin.plugin.impl.SpinProcessEnginePlugin" /> </list> </property> </bean> </beans>
Now you are able to use the Mock approach for CDI based delegates for plain java delegates as well:
@Test @Deployment(resources = "process.bpmn") public void testRunningProcess() throws Exception { // Registering a different service task implementation under its classname as used in the process definition Mocks.register("de.akquinet.camunda.FancyService", new MockedFancyService()); ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(PROCESS_DEFINITION_KEY); ... }
To have a full running example please check our Akquinet GitHub Camunda Examples.