Skip to content

Commit b9e845f

Browse files
committed
FIN-348 increase the ease of testing BPMN flows.
1 parent 9fc9a0f commit b9e845f

File tree

7 files changed

+276
-173
lines changed

7 files changed

+276
-173
lines changed

bpmn-process/src/main/resources/bpmn/core/process_scheduler.bpmn

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/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" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gaso3i" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.1">
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/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" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gaso3i" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.17.0">
33
<bpmn:process id="ProcessScheduler" name="Process Scheduler" isExecutable="true" camunda:versionTag="1.0.0" camunda:historyTimeToLive="P1000D">
44
<bpmn:documentation>For this scheduler flow the following variables must be set upon startup:
55

@@ -18,7 +18,7 @@
1818
</bpmn:extensionElements>
1919
<bpmn:outgoing>SequenceFlow_0nzbzo0</bpmn:outgoing>
2020
</bpmn:startEvent>
21-
<bpmn:sequenceFlow id="SequenceFlow_0nzbzo0" sourceRef="start" targetRef="Task_0o6bznh" />
21+
<bpmn:sequenceFlow id="SequenceFlow_0nzbzo0" sourceRef="start" targetRef="determine_delay" />
2222
<bpmn:sequenceFlow id="SequenceFlow_1uhkn62" sourceRef="scheduled_wait" targetRef="call_sub_process" />
2323
<bpmn:callActivity id="call_sub_process" name="Start process" calledElement="${subProcess}" camunda:variableMappingClass="com.jongsoft.finance.bpmn.delegate.scheduler.SchedulerVariableMappingDelegate">
2424
<bpmn:extensionElements>
@@ -32,17 +32,17 @@
3232
<bpmn:outgoing>sf_continue</bpmn:outgoing>
3333
<bpmn:outgoing>sf_terminate</bpmn:outgoing>
3434
</bpmn:exclusiveGateway>
35-
<bpmn:sequenceFlow id="sf_reschedule" sourceRef="call_sub_process" targetRef="Task_0o6bznh" />
35+
<bpmn:sequenceFlow id="sf_reschedule" sourceRef="call_sub_process" targetRef="determine_delay" />
3636
<bpmn:endEvent id="EndEvent_0jfl3x8">
3737
<bpmn:incoming>sf_terminate</bpmn:incoming>
3838
</bpmn:endEvent>
39-
<bpmn:sequenceFlow id="sf_delay" sourceRef="Task_0o6bznh" targetRef="st_determine_expired" />
40-
<bpmn:serviceTask id="Task_0o6bznh" name="Determine delay" camunda:class="com.jongsoft.finance.bpmn.delegate.scheduler.DetermineDelayDelegate">
39+
<bpmn:sequenceFlow id="sf_delay" sourceRef="determine_delay" targetRef="st_determine_expired" />
40+
<bpmn:serviceTask id="determine_delay" name="Determine delay" camunda:class="com.jongsoft.finance.bpmn.delegate.scheduler.DetermineDelayDelegate">
4141
<bpmn:incoming>SequenceFlow_0nzbzo0</bpmn:incoming>
4242
<bpmn:incoming>sf_reschedule</bpmn:incoming>
4343
<bpmn:outgoing>sf_delay</bpmn:outgoing>
4444
</bpmn:serviceTask>
45-
<bpmn:intermediateCatchEvent id="scheduled_wait" camunda:asyncBefore="true">
45+
<bpmn:intermediateCatchEvent id="scheduled_wait" name="wait">
4646
<bpmn:incoming>sf_continue</bpmn:incoming>
4747
<bpmn:outgoing>SequenceFlow_1uhkn62</bpmn:outgoing>
4848
<bpmn:timerEventDefinition>
@@ -64,14 +64,6 @@
6464
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="start">
6565
<dc:Bounds x="162" y="158" width="36" height="36" />
6666
</bpmndi:BPMNShape>
67-
<bpmndi:BPMNEdge id="SequenceFlow_0nzbzo0_di" bpmnElement="SequenceFlow_0nzbzo0">
68-
<di:waypoint x="198" y="176" />
69-
<di:waypoint x="261" y="176" />
70-
</bpmndi:BPMNEdge>
71-
<bpmndi:BPMNEdge id="SequenceFlow_1uhkn62_di" bpmnElement="SequenceFlow_1uhkn62">
72-
<di:waypoint x="832" y="176" />
73-
<di:waypoint x="940" y="176" />
74-
</bpmndi:BPMNEdge>
7567
<bpmndi:BPMNShape id="CallActivity_1ra326z_di" bpmnElement="call_sub_process">
7668
<dc:Bounds x="940" y="136" width="100" height="80" />
7769
</bpmndi:BPMNShape>
@@ -81,6 +73,29 @@
8173
<dc:Bounds x="628" y="206" width="63" height="14" />
8274
</bpmndi:BPMNLabel>
8375
</bpmndi:BPMNShape>
76+
<bpmndi:BPMNShape id="EndEvent_0jfl3x8_di" bpmnElement="EndEvent_0jfl3x8">
77+
<dc:Bounds x="1159" y="158" width="36" height="36" />
78+
</bpmndi:BPMNShape>
79+
<bpmndi:BPMNShape id="ServiceTask_0hv186u_di" bpmnElement="determine_delay">
80+
<dc:Bounds x="261" y="136" width="100" height="80" />
81+
</bpmndi:BPMNShape>
82+
<bpmndi:BPMNShape id="IntermediateCatchEvent_0tqk4z5_di" bpmnElement="scheduled_wait">
83+
<dc:Bounds x="796" y="158" width="36" height="36" />
84+
<bpmndi:BPMNLabel>
85+
<dc:Bounds x="805" y="201" width="20" height="14" />
86+
</bpmndi:BPMNLabel>
87+
</bpmndi:BPMNShape>
88+
<bpmndi:BPMNShape id="ServiceTask_1y7qhev_di" bpmnElement="st_determine_expired">
89+
<dc:Bounds x="449" y="136" width="100" height="80" />
90+
</bpmndi:BPMNShape>
91+
<bpmndi:BPMNEdge id="SequenceFlow_0nzbzo0_di" bpmnElement="SequenceFlow_0nzbzo0">
92+
<di:waypoint x="198" y="176" />
93+
<di:waypoint x="261" y="176" />
94+
</bpmndi:BPMNEdge>
95+
<bpmndi:BPMNEdge id="SequenceFlow_1uhkn62_di" bpmnElement="SequenceFlow_1uhkn62">
96+
<di:waypoint x="832" y="176" />
97+
<di:waypoint x="940" y="176" />
98+
</bpmndi:BPMNEdge>
8499
<bpmndi:BPMNEdge id="SequenceFlow_0jt1cqb_di" bpmnElement="sf_reschedule">
85100
<di:waypoint x="990" y="216" />
86101
<di:waypoint x="990" y="292" />
@@ -90,22 +105,10 @@
90105
<dc:Bounds x="645" y="222" width="13" height="14" />
91106
</bpmndi:BPMNLabel>
92107
</bpmndi:BPMNEdge>
93-
<bpmndi:BPMNShape id="EndEvent_0jfl3x8_di" bpmnElement="EndEvent_0jfl3x8">
94-
<dc:Bounds x="1159" y="158" width="36" height="36" />
95-
</bpmndi:BPMNShape>
96108
<bpmndi:BPMNEdge id="SequenceFlow_1fkf95e_di" bpmnElement="sf_delay">
97109
<di:waypoint x="361" y="176" />
98110
<di:waypoint x="449" y="176" />
99111
</bpmndi:BPMNEdge>
100-
<bpmndi:BPMNShape id="ServiceTask_0hv186u_di" bpmnElement="Task_0o6bznh">
101-
<dc:Bounds x="261" y="136" width="100" height="80" />
102-
</bpmndi:BPMNShape>
103-
<bpmndi:BPMNShape id="IntermediateCatchEvent_0tqk4z5_di" bpmnElement="scheduled_wait">
104-
<dc:Bounds x="796" y="158" width="36" height="36" />
105-
</bpmndi:BPMNShape>
106-
<bpmndi:BPMNShape id="ServiceTask_1y7qhev_di" bpmnElement="st_determine_expired">
107-
<dc:Bounds x="449" y="136" width="100" height="80" />
108-
</bpmndi:BPMNShape>
109112
<bpmndi:BPMNEdge id="SequenceFlow_1ymtsgk_di" bpmnElement="sf_should_continue">
110113
<di:waypoint x="549" y="176" />
111114
<di:waypoint x="631" y="176" />
Lines changed: 49 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,69 @@
11
package com.jongsoft.finance.bpmn;
22

3+
import com.jongsoft.finance.bpmn.process.ProcessExtension;
4+
import com.jongsoft.finance.bpmn.process.RuntimeContext;
35
import com.jongsoft.finance.schedule.Periodicity;
4-
import jakarta.inject.Inject;
6+
import org.apache.commons.lang3.mutable.MutableObject;
57
import org.assertj.core.api.Assertions;
6-
import org.camunda.bpm.engine.ProcessEngine;
8+
import org.junit.jupiter.api.DisplayName;
79
import org.junit.jupiter.api.Test;
810

911
import java.time.LocalDate;
1012
import java.time.ZoneId;
1113
import java.util.Date;
1214
import java.util.Map;
1315

14-
class ProcessSchedulerIT extends ProcessTestSetup {
15-
16-
@Inject
17-
private ProcessEngine processEngine;
16+
@ProcessExtension
17+
@DisplayName("Process Scheduler feature")
18+
class ProcessSchedulerIT {
1819

1920
@Test
20-
void runSchedule() {
21-
var process = processEngine.getRuntimeService().createProcessInstanceByKey("ProcessScheduler")
22-
.setVariable("subProcess", "EmptyProcess")
23-
.setVariable("start", "2019-01-02")
24-
.setVariable("end", LocalDate.now().plusMonths(5).toString())
25-
.setVariable("interval", 3)
26-
.setVariable("periodicity", Periodicity.MONTHS)
27-
.execute();
28-
29-
waitForSuspended(processEngine, process.getProcessInstanceId());
30-
31-
var subProcess = processEngine.getHistoryService()
32-
.createHistoricProcessInstanceQuery()
33-
.processDefinitionKey("EmptyProcess")
34-
.list();
35-
36-
Assertions.assertThat(subProcess).isEmpty();
21+
@DisplayName("Run a schedule, not yet due")
22+
void runSchedule(RuntimeContext context) {
23+
var execution = context.execute("ProcessScheduler", Map.of(
24+
"subProcess", "EmptyProcess",
25+
"start", "2019-01-02",
26+
"end", LocalDate.now().plusMonths(5).toString(),
27+
"interval", 3,
28+
"periodicity", Periodicity.MONTHS
29+
));
30+
31+
execution.verifyPendingActivity("scheduled_wait");
3732
}
3833

3934

4035
@Test
41-
void runSchedule_forceRun() throws InterruptedException {
42-
var process = processEngine.getRuntimeService().createProcessInstanceByKey("ProcessScheduler")
43-
.setVariable("subProcess", "EmptyProcess")
44-
.setVariable("start", "2019-01-02")
45-
.setVariable("end", LocalDate.now().plusYears(1).toString())
46-
.setVariable("interval", 3)
47-
.setVariable("periodicity", Periodicity.MONTHS)
48-
.setVariable("EmptyProcess", Map.of("subProcess-1", 1, "variable-2", "test"))
49-
.execute();
50-
51-
waitForSuspended(processEngine, process.getProcessInstanceId());
52-
53-
var nextRun = processEngine.getHistoryService()
54-
.createHistoricVariableInstanceQuery()
55-
.processDefinitionKey("ProcessScheduler")
56-
.processInstanceId(process.getProcessInstanceId())
57-
.variableName("nextRun")
58-
.singleResult();
59-
60-
var jobs = processEngine.getManagementService()
61-
.createJobQuery()
62-
.processInstanceId(process.getProcessInstanceId())
63-
.singleResult();
64-
65-
processEngine.getManagementService()
66-
.executeJob(jobs.getId());
67-
68-
waitForSuspended(processEngine, process.getProcessInstanceId());
69-
70-
var subProcess = processEngine.getHistoryService()
71-
.createHistoricProcessInstanceQuery()
72-
.processDefinitionKey("EmptyProcess")
73-
.singleResult();
74-
75-
Assertions.assertThat(subProcess).isNotNull();
76-
77-
var variableCount = processEngine.getHistoryService()
78-
.createHistoricVariableInstanceQuery()
79-
.processDefinitionKey("EmptyProcess")
80-
.count();
81-
82-
var scheduled = processEngine.getHistoryService()
83-
.createHistoricVariableInstanceQuery()
84-
.processDefinitionKey("EmptyProcess")
85-
.variableName("scheduled")
86-
.singleResult();
87-
88-
var var1 = processEngine.getHistoryService()
89-
.createHistoricVariableInstanceQuery()
90-
.processDefinitionKey("EmptyProcess")
91-
.variableName("subProcess-1")
92-
.singleResult();
93-
94-
Assertions.assertThat(variableCount).isEqualTo(4);
95-
Assertions.assertThat(var1.getValue()).isEqualTo(1);
96-
Assertions.assertThat(scheduled.getValue()).isEqualTo(LocalDate.ofInstant(((Date)nextRun.getValue()).toInstant(),
97-
ZoneId.systemDefault()).toString());
36+
@DisplayName("Run a schedule, force run to trigger next iteration")
37+
void runSchedule_forceRun(RuntimeContext context) throws InterruptedException {
38+
var execution = context.execute("ProcessScheduler", Map.of(
39+
"subProcess", "EmptyProcess",
40+
"start", "2019-01-02",
41+
"end", LocalDate.now().plusYears(1).toString(),
42+
"interval", 3,
43+
"periodicity", Periodicity.MONTHS,
44+
"EmptyProcess", Map.of("subProcess-1", 1, "variable-2", "test")
45+
));
46+
47+
MutableObject<Date> nextRun = new MutableObject<>();
48+
MutableObject<String> scheduled = new MutableObject<>();
49+
MutableObject<Integer> subValue = new MutableObject<>();
50+
51+
execution
52+
.verifyPendingActivity("scheduled_wait")
53+
.yankVariable("nextRun", nextRun::setValue)
54+
.forceJob("scheduled_wait")
55+
.verifyPendingActivity("scheduled_wait")
56+
.obtainChildProcess("EmptyProcess")
57+
.yankVariable("scheduled", scheduled::setValue)
58+
.yankVariable("subProcess-1", subValue::setValue)
59+
.verifyCompleted();
60+
61+
Assertions.assertThat(subValue.getValue()).isEqualTo(1);
62+
Assertions.assertThat(scheduled.getValue())
63+
.isEqualTo(
64+
LocalDate.ofInstant(
65+
nextRun.getValue().toInstant(),
66+
ZoneId.systemDefault()).toString());
9867
}
9968

100-
10169
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.jongsoft.finance.bpmn.process;
2+
3+
import org.assertj.core.api.Assertions;
4+
import org.camunda.bpm.engine.ProcessEngine;
5+
import org.camunda.bpm.engine.history.HistoricProcessInstance;
6+
7+
import java.util.function.Consumer;
8+
9+
public class HistoricProcessExecution implements ProcessTestExtension.ProcessExecution<HistoricProcessExecution> {
10+
11+
private final ProcessEngine processEngine;
12+
private final HistoricProcessInstance processInstance;
13+
14+
HistoricProcessExecution(ProcessEngine processEngine, HistoricProcessInstance processInstance) {
15+
this.processEngine = processEngine;
16+
this.processInstance = processInstance;
17+
}
18+
19+
@Override
20+
public HistoricProcessExecution obtainChildProcess(String processKey) {
21+
var subProcess = processEngine.getHistoryService()
22+
.createHistoricProcessInstanceQuery()
23+
.superProcessInstanceId(processInstance.getId())
24+
.processDefinitionKey(processKey)
25+
.singleResult();
26+
27+
Assertions.assertThat(subProcess)
28+
.as("Sub process '%s' not found", processKey)
29+
.isNotNull();
30+
31+
return new HistoricProcessExecution(processEngine, subProcess);
32+
}
33+
34+
@Override
35+
@SuppressWarnings("unchecked")
36+
public <Y> HistoricProcessExecution yankVariable(String variableName, Consumer<Y> consumer) {
37+
var variable = processEngine.getHistoryService()
38+
.createHistoricVariableInstanceQuery()
39+
.processInstanceIdIn(processInstance.getId())
40+
.variableName(variableName)
41+
.singleResult();
42+
43+
Assertions.assertThat(variable)
44+
.as("Variable %s not found", variableName)
45+
.isNotNull();
46+
47+
consumer.accept((Y) variable.getValue());
48+
return this;
49+
}
50+
51+
@Override
52+
public HistoricProcessExecution verifyCompleted() {
53+
Assertions.assertThat(processInstance.getEndTime())
54+
.as("Process '%s' not completed", processInstance.getProcessDefinitionKey())
55+
.isNotNull();
56+
57+
return this;
58+
}
59+
}

bpmn-process/src/test/java/com/jongsoft/finance/bpmn/process/ProcessExecution.java

Lines changed: 0 additions & 64 deletions
This file was deleted.

bpmn-process/src/test/java/com/jongsoft/finance/bpmn/process/ProcessTestExtension.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111

1212
import java.lang.reflect.AnnotatedElement;
1313
import java.util.List;
14+
import java.util.function.Consumer;
1415

1516
public class ProcessTestExtension extends MicronautJunit5Extension {
1617

18+
public interface ProcessExecution<T extends ProcessExecution> {
19+
ProcessExecution<?> obtainChildProcess(String processKey);
20+
T verifyCompleted();
21+
<Y> T yankVariable(String variableName, Consumer<Y> consumer);
22+
}
23+
1724
@Override
1825
protected void beforeEach(ExtensionContext context, @Nullable Object testInstance, @Nullable AnnotatedElement method, List<Property> propertyAnnotations) {
1926
super.beforeEach(context, testInstance, method, propertyAnnotations);

0 commit comments

Comments
 (0)