Processes in integration tests

Learn to start and execute processes in integration tests using the Bonita test toolkit.

Integrations tests are built upon the Bonita Test Toolkit, based on the open-source Bonita Java Client.
The Bonita Test Toolkit is only available for Enterprise, Performance, Efficiency, and Teamwork. editions.

Process definition

A process definition represents an existing process deployed on the targeted runtime. It is used to get general information on the process, manage process instances and process parameters.

Retrieve a process definition

A process definition can be retrieved using the Bonita test toolkit, the process name and optionally the process version. If the process version is not specified then the latest deployed version is retrieved (and not the latest version).

@BonitaTests
class ProcessIT {

    private static final String PROCESS_NAME = "myProcess";

    @Test
    void should_retrieve_process_definitions(BonitaTestToolkit toolkit) {
        ProcessDefinition processDef1 = toolkit.getProcessDefinition(PROCESS_NAME); (1)
        ProcessDefinition processDef2 = toolkit.getProcessDefinition(PROCESS_NAME,  "1.0.0"); (2)
    }
}
1 The process is retrieved without specifying the version → the latest deployed version is retrieved.
2 The process is retrieved from a given version.

Retrieve process initiators

The process initiators are the users that can start an instance of the process. They can be retrieved through the process definition.

@BonitaTests
class ProcessIT {

    @Test
    void should_retrieve_process_initiators(BonitaTestToolkit toolkit) {
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");
        Set<User> initiators = processDef.getInitiators();
    }
}

Start a process

A case can be started from the process definition. It requires a user to start the process and optionally a contract.

@BonitaTests
class ProcessIT {

    @Test
    void should_start_process_instances(BonitaTestToolkit toolkit) {
        User walter = toolkit.getUser("walter.bates");

        ProcessDefinition processDef1 = toolkit.getProcessDefinition("myProcess1");
        ProcessInstance processInstance1 = processDef1.startProcessFor(walter); (1)

        ProcessDefinition processDef2 = toolkit.getProcessDefinition("myProcess2");
        Contract contract = createContract(); (2)
        ProcessInstance processInstance2 = processDef2.startProcessFor(walter, contract); (3)
    }
}
1 An instance of myProcess1 is started for the user walter bates, without a contract.
2 A contract to instantiate the second process is created in a dedicated method (more details about contract creation here).
3 An instance of myProcess2 is started for the user walter bates with a contract.

Read and update process parameters

Process parameters are available from the process definition and can be updated.

If you update a process parameter it will be updated for all existing and coming process instances.

Parameter values are always retrieved and passed as String.

@BonitaTests
class ProcessIT {

    private static final String BOOLEAN_PARAMETER_NAME = "debugMode";

    @Test
    void should_update_process_parameter(BonitaTestToolkit toolkit) {
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");
        assertThat(processDef.getParameterValue(BOOLEAN_PARAMETER_NAME)).isEqualTo("false"); (1)
        processDef.updateParameterValue(BOOLEAN_PARAMETER_NAME, "true"); (2)
        assertThat(processDef.getParameterValue(BOOLEAN_PARAMETER_NAME)).isEqualTo("true"); (3)
    }
}
1 We check first that the debug mode is off → the parameter value is equal to 'false'.
2 We turn on the debug mode → the parameter value is equal to 'true'.
3 We finally check that the parameter value has been correctly updated.

Process instance

A process instance represents an existing case of a process, active or archived.
From a process instance, you can get some general information on the case, retrieve and execute tasks, and finally access all the elements of the case.

Delete existing instances

In order to have reproducible tests you may want to delete the process instances on the target runtime before running a test.

@BonitaTests
class ProcessIT {

    @BeforeEach
    void deleteProcessInstances(BonitaTestToolkit toolkit){
        toolkit.deleteProcessInstances(); (1)
    }

}
1 This will delete active and archived process instances on the target runtime. Be careful to never execute your tests on a production environment !

Tasks

The ProcessInstance object offers some convenient methods to retrieve the tasks of a case. If a task is a pending user task it can then be executed. More details about tasks here.

@BonitaTests
class ProcessIT {

    private static final String TASK_NAME = "aTask";

    @Test
    void retrieve_tasks(BonitaTestToolkit toolkit) {
        User walter = toolkit.getUser("walter.bates");
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");
        ProcessInstance processInstance = processDef.startProcessFor(walter);

        // Tasks
        List<Task> allTasks = processInstance.searchTasks(); (1)
        List<Task> allTasksWithAGivenName = processInstance.searchTasks(TASK_NAME); (2)
        Task firstTaskWithAGivenName = processInstance.getFirstTask(TASK_NAME); (3)

        // Pending user tasks
        List<UserTask> allPendingUserTasks = processInstance.searchPendingUserTasks(); (4)
        List<UserTask> allPendingUserTasksWithAGivenName = processInstance.searchPendingUserTasks(TASK_NAME); (5)
        UserTask firstPendingUserTaskWithAGivenName = processInstance.getFirstPendingUserTask(TASK_NAME); (6)
    }
}
1 Retrieve all the tasks (user, service, script…​) that have been executed or are ready.
2 Retrieve all the tasks (user, service, script…​) with a given name that have been executed or are ready. Several tasks can have the same name if they are in different processes, and one of the processes call the other one with a call activity.
3 Retrieve the first task (user, service, script…​) with a given name that has been executed or is ready.
4 Retrieve all the pending user tasks.
5 Retrieve all the pending user tasks with a given name.
6 Retrieve the first pending user task with a given name.

Case elements

Many elements of a case can be retrieved through the process instance:

Subprocess instances

When your process uses Call Activity or Event Subprocess, you may want to retrieve the ProcessInstance started by those.

Retrieve subprocess instances
@Test
void getSubprocessInstances(BonitaTestToolkit toolkit){

    var mainProcess = toolkit.getProcessDefinition("Main Process");
    var childProcess = toolkit.getProcessDefinition("Child Process");

    var mainInstance = mainProcess.startProcessFor(toolkit.getUser("jane"));

    // A Call activity has been executed and an instance of Child Process started
    await().until(mainInstance, ProcessInstancePredicates.hasSubprocessInstances(childProcess));

    List<ProcessInstance> subprocessInstances = mainInstance.getSubprocessInstances(0, 10); (1)
    List<ProcessInstance> childProcessInstances = mainInstance.getSubprocessInstances(childProcess, 0, 10); (2)
    ProcessInstance childInstance = mainInstance.getFirstStartedSubprocessInstance(childProcess); (3)
    ...

}
1 Returns the first 10 non archived ProcessInstance started by the mainInstance. Includes event subprocess instances and instances started by a call activity.
2 Returns the first 10 non archived ProcessInstance started by the mainInstance with Child Process definition. Only includes instances started by a call activity.
3 Returns the first non archived ProcessInstance started by the mainInstance with Child Process definition. Only includes instances started by a call activity.
Retrieve archived subprocess instances
@Test
void getArchivedSubprocessInstances(BonitaTestToolkit toolkit){

    var mainProcess = toolkit.getProcessDefinition("Main Process");
    var childProcess = toolkit.getProcessDefinition("Child Process");

    var mainInstance = mainProcess.startProcessFor(toolkit.getUser("jane"));

    // A Call activity has been executed and an instance of Child Process started and this instance is terminated and archvied

    List<ProcessInstance> archivedSubprocessInstances = mainInstance.getArchivedSubprocessInstances(0, 10); (1)
    List<ProcessInstance> archivedChildProcessInstances = mainInstance.getArchivedSubprocessInstances(childProcess, 0, 10); (2)
    List<ProcessInstance> allArchivedChildProcessInstances = childProcess.getArchivedProcessInstances(0, 10); (3)
    ...

}
1 Returns the first 10 archived ProcessInstance started by the mainInstance. Includes event subprocess instances and instances started by a call activity.
2 Returns the first 10 archived ProcessInstance started by the mainInstance with Child Process definition. Only includes instances started by a call activity.
3 Returns the first 10 archived ProcessInstance with Child Process definition. Either started by a call acticity or not.

Process predicates

In order to make asynchronous assertions on processes (using for example Awaitility), some convenient predicates come with the Bonita test toolkit. It allows ensuring in a scenario that the system is in the expected state before to continuing.
For example, it is possible to use the predicate hasProcessInstances(int numberOfProcessInstances) on a process definition to ensure that a case has been correctly started before continuing. Awaitility (or any other asynchronous library) will check this predicate at a given frequency, and will throw an error if the timeout is reached (i.e the case has not been started in time).

Process definition predicates

Here are the available predicates for definitions:

@BonitaTests
class ProcessIT {

    @Test
    void use_process_predicates(BonitaTestToolkit toolkit) {
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");
        User user = toolkit.getUser("walter.bates");

        await().until(processDef, ProcessDefinitionPredicates.canBeStartedBy(user)); (1)

        await().until(processDef, ProcessDefinitionPredicates.hasProcessInstances(1)); (2)

        await().until(processDef, ProcessDefinitionPredicates.hasArchivedProcessInstances(2)); (3)
    }
}
1 Verify that an instance of the process myProcess can be started by the user walter.bates
2 Verify that the process myProcess has 1 active process instance.
3 Verify that the process myProcess has 2 archived process instances.

Process instance predicates

Here are the available predicates for process instances:

import static com.bonitasoft.test.toolkit.predicate.ProcessDefinitionPredicates.*;

@BonitaTests
class ProcessIT {

    @Test
    void use_process_predicates(BonitaTestToolkit toolkit) {
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");
        User user = toolkit.getUser("walter.bates");

        ProcessInstance processInstance = processDef.startProcessFor(user);

        await("Process instance has been started by walter").until(processInstance, hasBeenStartedBy(user));
        await("Process instance is started").until(processInstance, processInstanceStarted());
        await("Process instance is completed").until(processInstance, processInstanceCompleted());
        await("Process instance is archived").until(processInstance, processInstanceArchived());
        await("Process instance is suspended").until(processInstance, processInstanceSuspended());
        await("Process instance is aborted").until(processInstance, processInstanceAborted());
        await("Process instance is cancelled").until(processInstance, processInstanceCancelled());
        await("Process instance has error").until(processInstance, processInstanceHasError());

        await("The user tasks 'task1' and 'task2' are pending").until(processInstance, containsPendingUserTasks("task1", "task2"));
        await("One flow node is active").until(processInstance, hasActiveFlowNodes(1));
        await("Two flow nodes failed").until(processInstance, hasFailedFlowNodes(0));
        await("The timer event trigger 'timer' is waiting").until(processInstance, hasTimerEventTrigger("timer"));
    }
}