11.14. Testing Business Processes

11.14.1. Unit Testing

You must design business processes at a high level with no implementation details. You must ensure that they are tested as they also have a lifecycle like other development artifacts and can be updated dynamically.
Unit tests are conducted to ensure processes behave as expected in specific use cases, for example, to test the output based on the specific input. The helper class JbpmJUnitTestCase (in the jbpm-test module) has been included to simplify unit testing. JbpmJUnitTestCase provides the following:
  • Helper methods to create a new knowledge base and session for a given set of processes.
  • Assert statements to check:
    • The state of a process instance (active, completed, aborted).
    • Which node instances are currently active.
    • Which nodes have been triggered (to check the path that has been followed).
    • The value of variables.
The image below contains a start event, a script task, and an end event. Within the example junit Test, a new session is created, the process is started, and the process instance is verified based on successful completion. It also checks whether these three nodes have been executed.
An example hello world process.

Figure 11.2. Example Hello World Process

Example 11.2. example junit Test

public class ProcessPersistenceTest extends JbpmJUnitBaseTestCase {


    public ProcessPersistenceTest() {

        // setup data source, enable persistence

        super(true, true);

    }


    @Test

    public void testProcess() {

        // create runtime manager with single process - hello.bpmn

        createRuntimeManager("hello.bpmn");

 

        // take RuntimeManager to work with process engine

        RuntimeEngine runtimeEngine = getRuntimeEngine();


        // get access to KieSession instance

        KieSession ksession = runtimeEngine.getKieSession();


        // start process

        ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello");


        // check whether the process instance has completed successfully

        assertProcessInstanceCompleted(processInstance.getId(), ksession);


        // check what nodes have been triggered

        assertNodeTriggered(processInstance.getId(), "StartProcess", "Hello", "EndProcess");

    }

}
}
The JbpmJUnitBaseTestCase method acts as base test case class that you can use for JBoss BPM Suite related tests. It provides four usage areas:
  • JUnit life cycle methods:
    • setUp: This method is executed @Before. It configures data source and EntityManagerFactory and cleans up Singleton's session id
    • tearDown: This method is executed @After. It clears out history, closes EntityManagerFactory and data source and disposes RuntimeEngines and RuntimeManager
  • Knowledge Base and KnowledgeSession management methods:
    • createRuntimeManager: This method creates RuntimeManager for given set of assets and selected strategy.
    • disposeRuntimeManager: This method disposes RuntimeManager currently active in the scope of test.
    • getRuntimeEngine: This method creates new RuntimeEngine for given context.
  • Assertions:
    • assertProcessInstanceCompleted
    • assertProcessInstanceAborted
    • assertProcessInstanceActive
    • assertNodeActive
    • assertNodeTriggered
    • assertProcessVarExists
    • assertNodeExists
    • assertVersionEquals
    • assertProcessNameEquals
  • Helper methods:
    • getDs: This method returns currently configured data source.
    • getEmf: This method returns currently configured EntityManagerFactory.
    • getTestWorkItemHandler: This method returns test work item handler that might be registered in addition to what is registered by default.
    • clearHistory: This method clears history log.
    • setupPoolingDataSource: This method sets up data source.
JbpmJUnitBaseTestCase supports all the predefined RuntimeManager strategies as part of the unit testing. It is enough to specify which strategy shall be used whenever creating runtime manager as part of single test. The following example uses PerProcessInstance runtime manager strategy and task service to deal with user tasks:
public class ProcessHumanTaskTest extends JbpmJUnitBaseTestCase {

    

    private static final Logger logger = LoggerFactory.getLogger(ProcessHumanTaskTest.class);


    public ProcessHumanTaskTest() {

        super(true, false);

    }

    

    @Test

    public void testProcessProcessInstanceStrategy() {

        RuntimeManager manager = createRuntimeManager(Strategy.PROCESS_INSTANCE, "manager", "humantask.bpmn");

        RuntimeEngine runtimeEngine = getRuntimeEngine(ProcessInstanceIdContext.get());

        KieSession ksession = runtimeEngine.getKieSession();

        TaskService taskService = runtimeEngine.getTaskService();

        

        int ksessionID = ksession.getId();

        ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello");


        assertProcessInstanceActive(processInstance.getId(), ksession);

        assertNodeTriggered(processInstance.getId(), "Start", "Task 1");

        

        manager.disposeRuntimeEngine(runtimeEngine);

        runtimeEngine = getRuntimeEngine(ProcessInstanceIdContext.get(processInstance.getId()));

        

        ksession = runtimeEngine.getKieSession();

        taskService = runtimeEngine.getTaskService();

        

        assertEquals(ksessionID, ksession.getId());

        

        // let john execute Task 1

        List<TaskSummary> list = taskService.getTasksAssignedAsPotentialOwner("john", "en-UK");

        TaskSummary task = list.get(0);

        logger.info("John is executing task {}", task.getName());

        taskService.start(task.getId(), "john");

        taskService.complete(task.getId(), "john", null);


        assertNodeTriggered(processInstance.getId(), "Task 2");

        

        // let mary execute Task 2

        list = taskService.getTasksAssignedAsPotentialOwner("mary", "en-UK");

        task = list.get(0);

        logger.info("Mary is executing task {}", task.getName());

        taskService.start(task.getId(), "mary");

        taskService.complete(task.getId(), "mary", null);


        assertNodeTriggered(processInstance.getId(), "End");

        assertProcessInstanceCompleted(processInstance.getId(), ksession);

    }

}

11.14.2. Testing Integration with External Services

Using domain-specific processes makes it possible to use testing handlers to verify whether or not specific services are requested correctly.
A TestWorkItemHandler is provided by default that can be registered to collect all work items (each work item represents one unit of work, for example, sending q specific email or invoking q specific service, and it contains all the data related to that task) for a given type. The test handler can be queried during unit testing to check whether specific work was actually requested during the execution of the process and that the data associated with the work was correct.
The following example describes how a process that sends an email could be tested. The test case tests whether an exception is raised when the email could not be sent (which is simulated by notifying the engine that sending the email could not be completed). The test case uses a test handler that simply registers when an email was requested and the data associated with the request. When the engine is notified the email could not be sent (using abortWorkItem(..)), the unit test verifies that the process handles this case successfully by logging this and generating an error, which aborts the process instance in this case.
An example image that illustrates how an email process could be tested.
public void testProcess2() {


    // create runtime manager with single process - hello.bpmn

    createRuntimeManager("sample-process.bpmn");

    // take RuntimeManager to work with process engine

    RuntimeEngine runtimeEngine = getRuntimeEngine();


    // get access to KieSession instance

    KieSession ksession = runtimeEngine.getKieSession();


    // register a test handler for "Email"

    TestWorkItemHandler testHandler = getTestWorkItemHandler();


    ksession.getWorkItemManager().registerWorkItemHandler("Email", testHandler);


    // start the process

    ProcessInstance processInstance = ksession.startProcess("com.sample.bpmn.hello2");


    assertProcessInstanceActive(processInstance.getId(), ksession);

    assertNodeTriggered(processInstance.getId(), "StartProcess", "Email");


    // check whether the email has been requested

    WorkItem workItem = testHandler.getWorkItem();

    assertNotNull(workItem);

    assertEquals("Email", workItem.getName());

    assertEquals("me@mail.com", workItem.getParameter("From"));

    assertEquals("you@mail.com", workItem.getParameter("To"));


    // notify the engine the email has been sent

    ksession.getWorkItemManager().abortWorkItem(workItem.getId());

    assertProcessInstanceAborted(processInstance.getId(), ksession);

    assertNodeTriggered(processInstance.getId(), "Gateway", "Failed", "Error");


}

11.14.3. Configuring Persistence

You can configure whether you want to execute the JUnit tests using persistence or not. By default, the JUnit tests will use persistence, meaning that the state of all process instances will be stored in a (in-memory H2) database (which is started by the JUnit test during setup) and a history log will be used to check assertions related to execution history. When persistence is not used, process instances will only live in memory and an in-memory logger is used for history assertions.
Persistence (and setup of data source) is controlled by the super constructor and allows following:
  • default: This is the no argument constructor and the most simple test case configuration (does NOT initialize data source and does NOT configure session persistence). It is usually used for in memory process management, without human task interaction
  • super(boolean, boolean): This allows to explicitly configure persistence and data source. This is the most common way of bootstrapping test cases for JBoss BPM Suite.
    • super(true, false): To execute with in-memory process management with human tasks persistence.
    • super(true, true): To execute with persistent process management with human tasks persistence
  • super(boolean, boolean, string): This is same as super(boolean, boolean), however it allows use of another persistence unit name than default (org.jbpm.persistence.jpa).
Here is an example:
public class ProcessHumanTaskTest extends JbpmJUnitBaseTestCase {

    

    private static final Logger logger = LoggerFactory.getLogger(ProcessHumanTaskTest.class);


    public ProcessHumanTaskTest() {

        // configure this tests to not use persistence for process engine but still use it for human tasks

        super(true, false);

    }

}