Skip to content

🧪 Testing Module ​

Beta

This module is in beta. The API may change in a future release. Leave feedback if you're using it.

The bpmn-to-code-testing library lets you write architecture tests for your BPMN process models — the same way ArchUnit lets you write architecture tests for Java code.

Add it to your test scope, write a test, and your CI will catch modeling issues before they reach production.

Dependency ​

kotlin
dependencies {
    testImplementation("io.github.emaarco:bpmn-to-code-testing:2.0.1")
}
xml
<dependency>
    <groupId>io.github.emaarco</groupId>
    <artifactId>bpmn-to-code-testing</artifactId>
    <version>2.0.1</version>
    <scope>test</scope>
</dependency>

Basic Usage ​

kotlin
import io.github.emaarco.bpmn.testing.BpmnValidator
import io.github.emaarco.bpmn.domain.shared.ProcessEngine

@Test
fun `BPMN models should have no violations`() {
    BpmnValidator
        .fromClasspath("bpmn/")           // loads all .bpmn files from classpath:bpmn/
        .engine(ProcessEngine.ZEEBE)
        .validate()
        .assertNoViolations()
}

Loading BPMN Files ​

MethodWhat it does
BpmnValidator.fromClasspath("bpmn/")Loads all .bpmn files from the given classpath path
BpmnValidator.fromDirectory(path)Loads all .bpmn files from a filesystem path (java.nio.file.Path)

Selecting Rules ​

By default, validate() runs all 11 built-in rules. You can override the rule set:

kotlin
BpmnValidator
    .fromClasspath("bpmn/")
    .engine(ProcessEngine.CAMUNDA_7)
    .withRules(BpmnRules.MISSING_SERVICE_TASK_IMPLEMENTATION, BpmnRules.MISSING_MESSAGE_NAME)
    .validate()
    .assertNoViolations()

Or pass a list:

kotlin
.withRules(BpmnRules.all().filter { it.severity == Severity.ERROR })

Disabling Rules ​

kotlin
BpmnValidator
    .fromClasspath("bpmn/")
    .engine(ProcessEngine.ZEEBE)
    .disableRules("invalid-identifier")
    .validate()
    .assertNoViolations()

Treating Warnings as Failures ​

kotlin
BpmnValidator
    .fromClasspath("bpmn/")
    .engine(ProcessEngine.ZEEBE)
    .failOnWarning()
    .validate()
    .assertNoViolations()

Assertions ​

validate() returns a BpmnValidationAssert with AssertJ-style assertions:

AssertionWhat it checks
.assertNoViolations()No violations at all (neither errors nor warnings)
.assertNoViolations("rule-id")No violations for the given rule
.assertHasViolations()At least one violation
.assertNoErrors()No ERROR-severity violations
.assertNoWarnings()No WARN-severity violations
.result()Returns the raw ValidationResult for custom assertions
kotlin
val result = BpmnValidator
    .fromClasspath("bpmn/")
    .engine(ProcessEngine.ZEEBE)
    .validate()

result.assertNoErrors()
result.assertNoViolations("invalid-identifier")  // custom: this rule is allowed to warn

Built-in Rules Reference ​

RuleBpmnRules constantSeverityTrigger
Service task has no implementationMISSING_SERVICE_TASK_IMPLEMENTATIONERRORService task without jobType / delegate
Message event has no nameMISSING_MESSAGE_NAMEERRORMessage start/catch/throw without a message name
Error event has no definitionMISSING_ERROR_DEFINITIONERRORError boundary/end event without error definition
Signal event has no nameMISSING_SIGNAL_NAMEERRORSignal start/intermediate/end without signal name
Timer event has no definitionMISSING_TIMER_DEFINITIONERRORTimer event without type or value
Call activity has no calledElementMISSING_CALLED_ELEMENTERRORCall activity without calledElement attribute
Flow node has no IDMISSING_ELEMENT_IDERRORAny flow node missing an id attribute
Process has no IDMISSING_PROCESS_IDERRORProcess element missing the id attribute
Process is emptyEMPTY_PROCESSERRORProcess with no flow nodes
Element ID produces invalid identifierINVALID_IDENTIFIERWARNID that cannot be converted to valid UPPER_SNAKE_CASE
Variable name collisionCOLLISION_DETECTIONERRORTwo different IDs normalize to the same constant name

Writing Custom Rules ​

Implement the BpmnValidationRule interface to add project-specific checks:

kotlin
class RequireElementPrefixRule : BpmnValidationRule {

    override val id = "require-element-prefix"
    override val severity = Severity.WARN

    override fun validate(context: ValidationContext): List<ValidationViolation> {
        return context.model.flowNodes
            .filter { !it.id.contains("_") }
            .map { node ->
                ValidationViolation(
                    ruleId = id,
                    severity = severity,
                    elementId = node.id,
                    processId = context.model.processId,
                    message = "Element '${node.id}' has no type prefix (e.g. 'Activity_', 'Task_').",
                )
            }
    }
}

Use it in tests:

kotlin
BpmnValidator
    .fromClasspath("bpmn/")
    .engine(ProcessEngine.ZEEBE)
    .withRules(*BpmnRules.all().toTypedArray(), RequireElementPrefixRule())
    .validate()
    .assertNoViolations()

ValidationContext

context.model gives you the full BpmnModel — flow nodes, service tasks, messages, signals, errors, timers, and variables. context.engine tells you which engine was selected, so you can write engine-specific rules.