Here is another update for working with Timer Occurrence counts:
Basically you provide a specific timer definition and a “count” which is the number of times the timer should be calculated forward in time. You can then compare this result against your predefined list:
Example:
class TimerTestSpec extends Specification implements bpmnTimers{
@Shared BpmnModelInstance model
@Shared SimpleDateFormat dateF = new SimpleDateFormat("yyyy MM dd - HH:mm")
@Shared SimpleDateFormat dateBiz = new SimpleDateFormat('E MMM FF HH:mm:ss zzz yyyy')
def setupSpec(){
String path = 'bpmn/qa-test.bpmn'
InputStream bpmnFile = this.class.getResource(path.toString()).newInputStream()
model = Bpmn.readModelFromStream(bpmnFile).withTraits(bpmnTimers)
}
def 'Start Event Cycle Cron Test - Timer Occurrences'(){
when:'Given a Timer Start Event that we get the 10 sequential occurrences'
String activityId = 'StartEvent_0ii048j'
TimerEventDefinition timerEvent = model.getTimerById(activityId)
Date customStartTime = dateF.parse('2011 01 01 - 00:00')
List<Date> timerOccurrences = getTimerOccurencesByCount(timerEvent, 10, customStartTime)
and: 'a expected set of dates are established'
List<Date> expectedTimerOccurrences = ['Sat Jan 01 01:00:00 EST 2011',
'Sat Jan 01 05:00:00 EST 2011',
'Sat Jan 01 10:00:00 EST 2011',
'Sat Jan 01 15:00:00 EST 2011',
'Sat Jan 01 20:00:00 EST 2011',
'Tue Feb 01 01:00:00 EST 2011',
'Tue Feb 01 05:00:00 EST 2011',
'Tue Feb 01 10:00:00 EST 2011',
'Tue Feb 01 15:00:00 EST 2011',
'Tue Feb 01 20:00:00 EST 2011'].collect {dateBiz.parse(it)}
then:'The timer should generate timer events for the following 10 dates'
assert timerOccurrences == expectedTimerOccurrences
// Use string comparison to show more detailed error to more easily
// identify where the error was in the list
// assert timerOccurrences.toString() == expectedTimerOccurrences.toString()
}
}
and the updated traits are:
trait bpmnTimers{
// reference: https://github.com/camunda/camunda-bpm-platform/tree/master/engine/src/main/java/org/camunda/bpm/engine/impl/calendar
// https://docs.camunda.org/manual/7.9/reference/bpmn20/events/timer-events
Collection<TimerEventDefinition> getTimers(){
BpmnModelInstance model = (BpmnModelInstance)this
Collection<TimerEventDefinition> timerEventDefinitions = model.getModelElementsByType(TimerEventDefinition.class)
return timerEventDefinitions
}
TimerEventDefinition getTimerById(String activityId){
BpmnModelInstance model = (BpmnModelInstance)this
TimerEventDefinition timerEventDefinition = model.getModelElementsByType(TimerEventDefinition.class).find {
it.getParentElement().getAttributeValue('id') == activityId
}
return timerEventDefinition
}
Map<String, Date> evaluateTimers(Date customCurrentTime = null) {
BpmnModelInstance model = (BpmnModelInstance) this
Collection<TimerEventDefinition> timerEventDefinitions = model.getModelElementsByType(TimerEventDefinition.class)
Map<String, Date> timers = [:]
println customCurrentTime
if (customCurrentTime){
setCurrentTime(customCurrentTime)
}
println ClockUtil.getCurrentTime()
timerEventDefinitions.each { timer ->
Map<String, Date> timerEval = evaluateTimer(timer)
timers.putAll(timerEval)
}
if (customCurrentTime) {
resetCurrentTime()
}
return timers
}
private void setCurrentTime(Date customCurrentTime){
ClockUtil.setCurrentTime(customCurrentTime)
}
private void resetCurrentTime(){
ClockUtil.reset()
}
List<Date> getTimerOccurencesByCount(TimerEventDefinition timer, Integer count, Date customCurrentTime = null){
String activityId = timer.getParentElement().getAttributeValue('id')
if (activityId == null){
throw new IOException('Could not get Activity Id of Timer Event Definition')
}
if (customCurrentTime){
setCurrentTime(customCurrentTime)
}
List<Date> timerOccurrences = new ArrayList<Date>()
Date timerEvalDate = customCurrentTime
1.upto(count, {
Date evalResult = evaluateTimer(timer, timerEvalDate).get(activityId)
timerEvalDate = evalResult
timerOccurrences << evalResult
})
return timerOccurrences
}
Map<String, Date> evaluateTimer(TimerEventDefinition timer, Date customCurrentTime = null){
Map<String,String> timerInfo = getTimerValue(timer)
String activityId = timer.getParentElement().getAttributeValue('id')
if (activityId == null){
throw new IOException('Could not get Activity Id of Timer Event Definition')
}
if (customCurrentTime){
setCurrentTime(customCurrentTime)
}
switch (timerInfo) {
case { it.type == 'date'}:
DueDateBusinessCalendar dueDateCalendar = new DueDateBusinessCalendar()
Date dueDate = dueDateCalendar.resolveDuedate(timerInfo.value)
if (customCurrentTime){
resetCurrentTime()
}
return [(activityId) : dueDate]
case {it.type == 'cycle'}:
CycleBusinessCalendar cycleBusinessCalendar = new CycleBusinessCalendar()
Date cycleDueDate = cycleBusinessCalendar.resolveDuedate(timerInfo.value)
if (customCurrentTime){
resetCurrentTime()
}
return [(activityId) : cycleDueDate]
case {it.type == 'duration'}:
DurationBusinessCalendar durationCalendar = new DurationBusinessCalendar()
Date durationDueDate = durationCalendar.resolveDuedate(timerInfo.value)
if (customCurrentTime){
resetCurrentTime()
}
return [(activityId) : durationDueDate]
default:
throw new IOException('Invalid Timer mapping found: must be of type: date or cycle or duration')
}
}
Map<String, String> getTimerValue(TimerEventDefinition timer) {
if (timer.getTimeDate() != null) {
return [('type'):'date',
('value'): timer.getTimeDate().getRawTextContent()]
} else if (timer.getTimeCycle() != null) {
return [('type'):'cycle',
('value'): timer.getTimeCycle().getRawTextContent()]
} else if (timer.getTimeDuration() != null) {
return [('type'):'duration',
('value'): timer.getTimeDuration().getRawTextContent()]
} else {
throw new IOException('Timer definition missing; Timer definition is required on all timers')
}
}
}
specifically the following method:
...
List<Date> getTimerOccurencesByCount(TimerEventDefinition timer, Integer count, Date customCurrentTime = null){
String activityId = timer.getParentElement().getAttributeValue('id')
if (activityId == null){
throw new IOException('Could not get Activity Id of Timer Event Definition')
}
if (customCurrentTime){
setCurrentTime(customCurrentTime)
}
List<Date> timerOccurrences = new ArrayList<Date>()
Date timerEvalDate = customCurrentTime
1.upto(count, {
Date evalResult = evaluateTimer(timer, timerEvalDate).get(activityId)
timerEvalDate = evalResult
timerOccurrences << evalResult
})
return timerOccurrences
}
...
This lets us get a list of the timers occurrences that would match execution, and this easily test a expected list of sequences occurrence of timer executions against the list.
So our Unit test has the important parts of:
...
List<Date> timerOccurrences = getTimerOccurencesByCount(timerEvent, 10, customStartTime)
...
Where we run the timerEvent by 10 occurrence using our customStartTime. Each occurrence of the timer will move time forward to the moment of the previous timer.
We can then set the expected values:
...
and: 'a expected set of dates are established'
List<Date> expectedTimerOccurrences = ['Sat Jan 01 01:00:00 EST 2011',
'Sat Jan 01 05:00:00 EST 2011',
'Sat Jan 01 10:00:00 EST 2011',
'Sat Jan 01 15:00:00 EST 2011',
'Sat Jan 01 20:00:00 EST 2011',
'Tue Feb 01 01:00:00 EST 2011',
'Tue Feb 01 05:00:00 EST 2011',
'Tue Feb 01 10:00:00 EST 2011',
'Tue Feb 01 15:00:00 EST 2011',
'Tue Feb 01 20:00:00 EST 2011'].collect {dateBiz.parse(it)}
then:'The timer should generate timer events for the following 10 dates'
assert timerOccurrences == expectedTimerOccurrences
...
and assert that the actual occurrences and the expected occurrences match.
No need to run execution of the BPMN model!
Enjoy!
and note: for the business analysts that might write some of these tests, you can further simpify the unit test by removing the “typing” and just use things like def
:
...
and: 'a expected set of dates are established'
def expectedTimerOccurrences = ['Sat Jan 01 01:00:00 EST 2011',
'Sat Jan 01 05:00:00 EST 2011',
'Sat Jan 01 10:00:00 EST 2011',
'Sat Jan 01 15:00:00 EST 2011',
'Sat Jan 01 20:00:00 EST 2011',
'Tue Feb 01 01:00:00 EST 2011',
'Tue Feb 01 05:00:00 EST 2011',
'Tue Feb 01 10:00:00 EST 2011',
'Tue Feb 01 15:00:00 EST 2011',
'Tue Feb 01 20:00:00 EST 2011'].collect {dateBiz.parse(it)}
then:'The timer should generate timer events for the following 10 dates'
assert timerOccurrences == expectedTimerOccurrences
...
No need to worry about getting the typing correct
Edit:
For those wondering you get error output such as:
timerOccurrences == expectedTimerOccurrences
| | |
| | [Sat Jan 01 01:10:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
| false
[Sat Jan 01 01:00:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
Or if you compare as strings, you can more robust information:
timerOccurrences.toString() == expectedTimerOccurrences.toString()
| | | | |
| | | | [Sat Jan 01 01:10:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
| | | [Sat Jan 01 01:10:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
| | false
| | 1 difference (95% similarity) (comparing subset start: 4, end1: 26, end2: 26)
| | Jan 01 01:(0)0:00 EST 2
| | Jan 01 01:(1)0:00 EST 2
| [Sat Jan 01 01:00:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
[Sat Jan 01 01:00:00 EST 2011, Sat Jan 01 05:00:00 EST 2011, Sat Jan 01 10:00:00 EST 2011, Sat Jan 01 15:00:00 EST 2011, Sat Jan 01 20:00:00 EST 2011, Tue Feb 01 01:00:00 EST 2011, Tue Feb 01 05:00:00 EST 2011, Tue Feb 01 10:00:00 EST 2011, Tue Feb 01 15:00:00 EST 2011, Tue Feb 01 20:00:00 EST 2011]
For longer lists of dates, the use of strings will be beneficial because you will be able to easily identify exactly where the issue is.