How to trigger a boundary timer event of type 'cycle' within JUnit?

Hello,

I’m just playing around with Camunda and JUnit. Actually, I cannot solve the following problem:

In my test process I have a user task with a non-interrupting boundary timer event of type ‘cycle’. I have defined the cycle with 2 rounds of 1 hour. Each round should trigger a service task.

In my tests with JUnit, I’ve tried to trigger one round of the timer event with the following lines: (pi is my running process instance)

Job job = processEngine().getManagementService().createJobQuery().processInstanceId(pi.getId()).timers().singleResult(); processEngine().getManagementService().executeJob(job.getId());

Running the test, an exception is thrown:

org.camunda.bpm.engine.ProcessEngineException: Query return 2 results instead of max 1

It seems that both rounds of my timer event have the same jobId.

What is the correct way, to trigger (one round of) my timer of type ‘cycle’?

Thanks for any advice!

Cheers,
Kerstin

Hi Kerstin,

Please post the BPMN 2.0 XML of the process and the entire code of the test method.

Thanks,
Thorben

Hi Thorben,

here is the BPMN 2.0 XML:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.1.1">
  <bpmn:process id="approve-loan" name="Loan Approval" isExecutable="true">
    <bpmn:startEvent id="StartEvent_1" name="Loan Request Received" camunda:formKey="embedded:app:forms/request-loan.html">
      <bpmn:outgoing>SequenceFlow_02daavj</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:sequenceFlow id="SequenceFlow_02daavj" sourceRef="StartEvent_1" targetRef="UserTask_approve" />
    <bpmn:userTask id="UserTask_approve" name="Approve Loan" camunda:formKey="embedded:app:forms/approve-loan.html" camunda:assignee="demo">
      <bpmn:incoming>SequenceFlow_02daavj</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_09mev4b</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:endEvent id="EndEvent_approved" name="Loan Request Approved">
      <bpmn:incoming>SequenceFlow_1fu87cy</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="SequenceFlow_09mev4b" sourceRef="UserTask_approve" targetRef="ExclusiveGateway_0uivs63" />
    <bpmn:sequenceFlow id="SequenceFlow_1fu87cy" sourceRef="ServiceTask_process" targetRef="EndEvent_approved" />
    <bpmn:serviceTask id="ServiceTask_process" name="Process Request" camunda:class="org.camunda.bpm.getstarted.loanapproval.ProcessRequestDelegate">
      <bpmn:incoming>SequenceFlow_16ykv5v</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_1fu87cy</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="ExclusiveGateway_0uivs63" name="approved?">
      <bpmn:incoming>SequenceFlow_09mev4b</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_16ykv5v</bpmn:outgoing>
      <bpmn:outgoing>SequenceFlow_1dlgaga</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:sequenceFlow id="SequenceFlow_16ykv5v" name="yes" sourceRef="ExclusiveGateway_0uivs63" targetRef="ServiceTask_process">
      <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">#{approved}</bpmn:conditionExpression>
    </bpmn:sequenceFlow>
    <bpmn:sequenceFlow id="SequenceFlow_1dlgaga" name="no" sourceRef="ExclusiveGateway_0uivs63" targetRef="EndEvent_declined">
      <bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">#{!approved}</bpmn:conditionExpression>
    </bpmn:sequenceFlow>
    <bpmn:endEvent id="EndEvent_declined" name="Loan Request declined">
      <bpmn:extensionElements>
        <camunda:executionListener class="org.camunda.bpm.getstarted.loanapproval.LoanRequestDeclinedListener" event="end" />
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_1dlgaga</bpmn:incoming>
      <bpmn:incoming>SequenceFlow_1kb4pzq</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="BoundaryEvent_error" attachedToRef="ServiceTask_process">
      <bpmn:outgoing>SequenceFlow_1kb4pzq</bpmn:outgoing>
      <bpmn:errorEventDefinition errorRef="processError" />
    </bpmn:boundaryEvent>
    <bpmn:sequenceFlow id="SequenceFlow_1kb4pzq" sourceRef="BoundaryEvent_error" targetRef="EndEvent_declined" />
    <bpmn:sequenceFlow id="SequenceFlow_0r4y05w" sourceRef="BoundaryEvent_0im7prj" targetRef="ServiceTask_sendReminder" />
    <bpmn:serviceTask id="ServiceTask_sendReminder" name="Send reminder" camunda:class="org.camunda.bpm.getstarted.loanapproval.SendReminderDelegate">
      <bpmn:incoming>SequenceFlow_0r4y05w</bpmn:incoming>
    </bpmn:serviceTask>
    <bpmn:boundaryEvent id="BoundaryEvent_0im7prj" cancelActivity="false" attachedToRef="UserTask_approve">
      <bpmn:outgoing>SequenceFlow_0r4y05w</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeCycle xsi:type="bpmn:tFormalExpression">R2/PT1H</bpmn:timeCycle>
      </bpmn:timerEventDefinition>
    </bpmn:boundaryEvent>
  </bpmn:process>
  <bpmn:error id="processError" name="PROCESSING_REQUEST_FAILED_ERROR" />
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="approve-loan">
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="173" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_02daavj_di" bpmnElement="SequenceFlow_02daavj">
        <di:waypoint xsi:type="dc:Point" x="209" y="120" />
        <di:waypoint xsi:type="dc:Point" x="301" y="120" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="210" y="95" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="UserTask_1pl8ok7_di" bpmnElement="UserTask_approve">
        <dc:Bounds x="301" y="80" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_1xliwh4_di" bpmnElement="EndEvent_approved">
        <dc:Bounds x="963" y="102" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="936" y="138" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_09mev4b_di" bpmnElement="SequenceFlow_09mev4b">
        <di:waypoint xsi:type="dc:Point" x="401" y="120" />
        <di:waypoint xsi:type="dc:Point" x="493" y="120" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="790" y="95" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_1fu87cy_di" bpmnElement="SequenceFlow_1fu87cy">
        <di:waypoint xsi:type="dc:Point" x="880" y="120" />
        <di:waypoint xsi:type="dc:Point" x="963" y="120" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="1006" y="95" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ServiceTask_0y0uoa6_di" bpmnElement="ServiceTask_process">
        <dc:Bounds x="780" y="80" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ExclusiveGateway_0uivs63_di" bpmnElement="ExclusiveGateway_0uivs63" isMarkerVisible="true">
        <dc:Bounds x="493" y="95" width="50" height="50" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="473" y="70" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_16ykv5v_di" bpmnElement="SequenceFlow_16ykv5v">
        <di:waypoint xsi:type="dc:Point" x="543" y="120" />
        <di:waypoint xsi:type="dc:Point" x="780" y="120" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="616.5" y="95" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_1dlgaga_di" bpmnElement="SequenceFlow_1dlgaga">
        <di:waypoint xsi:type="dc:Point" x="518" y="145" />
        <di:waypoint xsi:type="dc:Point" x="518" y="300" />
        <di:waypoint xsi:type="dc:Point" x="963" y="300" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="488" y="212.5" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="EndEvent_0rnitih_di" bpmnElement="EndEvent_declined">
        <dc:Bounds x="963" y="282" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="936" y="318" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="BoundaryEvent_14itx0f_di" bpmnElement="BoundaryEvent_error">
        <dc:Bounds x="815" y="142" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="788" y="178" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_1kb4pzq_di" bpmnElement="SequenceFlow_1kb4pzq">
        <di:waypoint xsi:type="dc:Point" x="833" y="178" />
        <di:waypoint xsi:type="dc:Point" x="833" y="300" />
        <di:waypoint xsi:type="dc:Point" x="963" y="300" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="803" y="229" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_0r4y05w_di" bpmnElement="SequenceFlow_0r4y05w">
        <di:waypoint xsi:type="dc:Point" x="370" y="178" />
        <di:waypoint xsi:type="dc:Point" x="370" y="260" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="304" y="178" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ServiceTask_0i7ey4b_di" bpmnElement="ServiceTask_sendReminder">
        <dc:Bounds x="320" y="260" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="BoundaryEvent_0im7prj_di" bpmnElement="BoundaryEvent_0im7prj">
        <dc:Bounds x="352" y="142" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="325" y="178" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

And the complete test method:

    @Test
    @Deployment(resources="loanapproval.bpmn")
    public void testSendReminder() {
        
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("customerId", "testCustomer123");
        variables.put("amount", 100.0);
        
        ProcessInstance pi = processEngine().getRuntimeService().startProcessInstanceByKey("approve-loan", variables);
        
        assertThat(pi).isWaitingAt("UserTask_approve");
        
        Job job = processEngine().getManagementService().createJobQuery().processInstanceId(pi.getId()).timers().singleResult();
        processEngine().getManagementService().executeJob(job.getId());
    
        assertThat(pi).hasPassed("ServiceTask_sendReminder").isWaitingAt("UserTask_approve");
        
        Task task = processEngine().getTaskService().createTaskQuery().taskDefinitionKey("UserTask_approve").singleResult();
        
        variables.put("approved", true);
        processEngine().getTaskService().complete(task.getId(), variables);
        assertThat(pi).hasPassed("ServiceTask_process").isEnded().hasPassed("EndEvent_approved");
    }

Cheers,
Kerstin

2 Likes

Hi Kerstin,

The test works fine for me. Could it be that data from a previous test case is not cleaned up, so that a job remains the database?

Cheers,
Thorben

Thanks! That was the problem. It’s working for me now, too.

Just out of interest: Why it is possible to model an interrupting boundary timer event of type ‘cycle’ to a user task? I guess there will always be an exception after the first round was triggered because the user task instance does no longer exist. Is there any use case where such a boundary event is useful?

Cheers,
Kerstin

Hi Kerstin,

Having such a boundary event is not useful :slight_smile:
I think we just do not have proper validation for this case in place.

I expect the behavior to be that of a duration configuration, i.e. the first cycle is executed and then nothing more happens.

Cheers,
Thorben