Why teams unit test Flowable processes with JUnit 5
JUnit 5 tests can deploy a BPMN process, start instances, complete tasks and assert on process state, variables and the paths an instance takes, so process logic can be checked on every change. Many teams start with the opposite. The process lives in BPMN files, a developer edits a gateway condition or a timer duration, and the only check is someone clicking through a shared environment before release. That manual pass tends to miss the branch nobody tried, the boundary error event that only fires when a downstream call fails, and the timer that behaves differently after a deadline. Automated tests make those paths explicit and repeatable, and they fail in the pull request instead of in a staging queue.
Flowable's test support is built around the engine that runs production. The Flowable docs and the flowable-engine repository on GitHub describe the test facilities this topic covers, including FlowableTest and FlowableExtension, which manage engine startup and let a test load @Deployment resources and configuration resources. Because the tests run the same engine code, the same BPMN parsing and the same execution logic, a passing test says something about how the process will behave in production, not about a stand-in for it. Confirm the exact annotations and their behavior in the documentation for the version you run, because test support changes with each engine release.
The engine version sets the JUnit baseline. Flowable 8.0.0, released on February 27, 2026, removed JUnit 3 and 4 support, so a team on Flowable 8 writes its process tests in JUnit 5. A team still on an earlier line should check which JUnit version its test support targets before upgrading. The Flowable 8.0.0 release notes also move the baseline to Spring Framework 7 and Spring Boot 4, so decide the JUnit version together with the Spring baseline. Settle this before writing the first test, because a suite ported later has to change every test class at once.
Choosing a test setup: plain engine, Spring Boot or the FlowableTest annotation
A plain Java SE test starts the Flowable engine as a library inside the test JVM, with an in-memory H2 database created for the run. The test builds the engine configuration, deploys the BPMN resources under test with @Deployment, and drives instances through start, wait and complete steps. The JUnit 5 support, through FlowableTest and FlowableExtension, manages the engine lifecycle, while a configuration resource on the test classpath sets the engine properties for the run.
This setup leaves out the Spring container, the beans your service tasks depend on, framework-managed transactions and any behavior specific to the database vendor you run in production. H2 is one of the supported database types in the configuration docs, but it does not reproduce the SQL behavior, locking or timestamp handling of your production database.
- Fast feedback. Engine startup and deployment happen in the test process, so the suite runs without external services.
- Explicit wiring. Delegates and other beans must be registered in the test configuration, so the test shows what the process depends on.
- Configuration resource. Keep a test configuration file separate from production so that test-only settings do not leak into deployments.
- Jobs and timers. Timers and async jobs run on the async executor. Decide whether each test waits for jobs or triggers them directly, and confirm the approach with your Flowable version.
- Shared state. Engine caching across tests can hide leftover data, so reset or isolate state between tests.
Confirm that the test configuration uses the same history and job settings as production, because a test that passes under different settings proves less about the deployed process.
In a Spring or Spring Boot application, the Flowable engine runs inside the application context, so tests can load the same beans, configuration and starters that the service uses. The Spring Boot docs describe starters for all engines, for REST, and for the individual process, CMMN and DMN engines. A test slice or full context test wires delegates, expression beans and Flowable services exactly as the running application does, which makes it the closest setup to production code paths without a separate database.
The cost is startup time and a larger configuration surface. Context-based tests load every bean the slice touches, and a mismatch in starter versions can break the suite before any process logic runs. The Flowable 8.0.0 release notes describe an upgrade to Spring Framework 7 and Spring Boot 4, with no backward compatibility for Spring Boot 3, so the test stack must follow the application's framework line.
- Shared context. Reuse the Spring context across test classes where possible to limit startup cost.
- Bean substitution. Replace external services with test doubles while keeping Flowable beans real.
- Starter alignment. Keep the Flowable starters and Spring Boot version identical to the application.
- Actuator checks. The
/actuator/flowableendpoint can help confirm what the running engine reports, which is useful when a test fails for environmental reasons. - Transactions. Framework-managed transactions change when state is committed, so assert on committed results where that matters.
Check which Spring Boot line your application uses before choosing this setup, because the Flowable release line determines the supported framework baseline.
Some process behavior depends on the database. Timestamps, identifier generation, row locking and the way the async executor coordinates nodes through lock owners and lock expiration times all run against the database the engine uses. A test that must match production runs the same Flowable engine against the same database vendor, version and driver that the environment uses, usually in a container or a dedicated test instance.
The Flowable configuration docs list H2, MySQL, Oracle, PostgreSQL, DB2 and Microsoft SQL Server as supported database types. The docs also note that MySQL versions below 5.6.4 lack millisecond precision, which can change ordering and timer assertions. This setup is slower to start and needs database provisioning, credentials and cleanup in your pipeline, so it is usually reserved for a smaller set of integration tests.
- Vendor-specific behavior. Run timer, due date and history assertions against the real vendor to catch precision and SQL differences.
- Schema lifecycle. Decide how the schema is created and dropped between runs, and keep that step in the pipeline.
- Credentials. Keep database credentials in your secrets store, not in test resource files.
- Parallel runs. Give each pipeline job its own schema or database so concurrent builds do not share rows.
- Version pinning. Pin the database version in the test environment to the one you run in production.
Ask your team to confirm the database vendor, version and driver in production, then use the same values in the test environment before trusting timer or history results.
Test configuration options and when each fits
This table compares the main ways to configure a Flowable JUnit 5 test: which deployment resources load, how the engine is configured, whether Spring wiring is involved, and which database runs the tests. Read each row against the process under test, since the right choice depends on whether the test checks BPMN logic or database-dependent behavior. The main takeaway is that fast in-memory tests cover most logic, while a production-like database is needed for behavior that depends on the real database.
| Option | What it controls | When it fits | Trade-off |
|---|---|---|---|
| @Deployment resources | Loads the BPMN, CMMN or DMN files named by path into the test engine before each test | Testing one process or case definition in isolation with fixed model files | Each named file is deployed for the test, so large suites with many files slow down |
| Configuration resources | Supplies a Flowable engine configuration file that sets engine options for the test | Matching production engine settings such as job executor or history level | A custom config can drift from production if it is not kept in sync |
| Spring test context | Creates the engine through Spring Boot test wiring so beans, delegates and expressions resolve | Process models that call Spring beans through delegateExpression or expression | Context startup adds time and mocks must be set up to isolate external services |
| Plain engine without Spring | Builds the engine directly in the test using Flowable's Java configuration | Pure BPMN logic tests with plain Java delegates and no Spring beans | Spring-only expressions fail to resolve, so those tests need a different setup |
| In-memory H2 | Runs the database in memory and resets it between runs | Fast feedback for gateways, timers, variables and error paths | H2 may hide SQL and dialect differences from production databases |
| Production-like database | Runs the same database product and version as production, often in a container | Checking locking, timer recovery, history queries or vendor-specific behavior | Slower startup and more infrastructure to run in CI |
| Shared engine across tests | Keeps one engine instance and its caches between tests in the same run | Large suites where startup cost dominates | Leftover state or cached definitions can leak between tests |
| Fresh engine per test | Creates a new engine for each test method | Tests that change engine configuration or depend on clean state | Startup cost is repeated for every test |
How to set up a Flowable JUnit 5 test suite
This sequence takes a Flowable project from dependencies to a first passing process test, so each later test runs against the same deployed BPMN resources and engine configuration. The output is a test class that deploys real process files, starts an instance, and asserts on its state.
Add Flowable and JUnit 5 test dependencies
The developer adds the Flowable test support artifacts for the engine in use (BPMN, CMMN or DMN) to the build file, alongside JUnit 5 in the test scope, using the versions the project already manages. Check the Flowable documentation for the exact artifact names that match your Flowable version, because the test support classes differ between major releases. Done when the project compiles and the build resolves the Flowable test classes without warnings about missing types.
Place BPMN resources under test resources
The developer copies the process files that the test will exercise into the test resources folder, keeping the same paths and file names the production application uses so the tests do not drift from the real model. Test-only mock delegates or stub expressions can sit in the same test source tree. Done when the process files are visible on the test classpath and open cleanly in a BPMN modeler.
Annotate the test class with FlowableTest and Deployment
The developer creates a JUnit 5 test class, applies the FlowableTest annotation so Flowable's JUnit 5 extension manages the engine lifecycle, and adds a Deployment annotation that names the BPMN resource to deploy before each test. The Deployment annotation can list one or several resource paths. Done when a trivial test method runs and the extension reports that the named resource was deployed without error.
Configure the engine for tests
The developer provides the configuration resource the extension uses, typically a test-specific engine configuration that points at an in-memory database such as H2 and sets the options the tests depend on, such as job executor behavior for timers. The team confirms which database type the test configuration uses and keeps it separate from production settings. Done when the engine starts in the test run and the configuration resource is picked up as intended.
Confirm the deployment and process key
Before writing behavior tests, the developer adds a short test that queries the repository service for the deployed definition and asserts that the expected process definition key resolves to a version. This catches wrong resource paths, XML errors and key typos before they show up as confusing failures in later tests. Done when this check passes and the definition key matches the value the application uses to start the process.
Write the first test that starts and checks an instance
The developer writes a test that starts a process instance with a known business key and variables through the runtime service, then queries for the active tasks or the instance itself and asserts on the expected state. The test should assert on the activity or task the process reaches, not only that a call returned. Done when the test passes, fails visibly if the model changes the expected path, and leaves no state that affects the next test.
Confirm isolation between tests
The team runs the test class twice in different orders and checks that each test starts from a clean state, because the extension can reuse the engine across tests for speed. The developer confirms that instances and deployments created by one test do not change the results of another. Done when repeated runs in different orders give identical results, and the team records any clean-up it needs to add.
Covering gateways, timers and error paths in Flowable tests
Each branch in a Flowable process needs its own test, and the cheapest way to reach a branch is to set the variables the gateway reads before the process reaches it. For an exclusive gateway, start the instance with variables that satisfy one condition at a time, then assert which activity is active or which end event was reached. For a parallel split, do not assert only the outcome: query the active activities after the fork and confirm that each branch has a task or wait state, so a join that never fires is caught. Use the variable names your gateway conditions read as the contract for these tests, and keep the expressions in the model rather than duplicating their logic in Java assertions. Confirm how your engine version exposes runtime and task queries in the Flowable Java API documentation before you write the helpers.
Timers need more care than gateways because the async executor, not the test thread, normally fires them. The Flowable advanced documentation describes the executor as a thread pool that runs timers and asynchronous jobs, so a test that simply waits for a boundary timer to elapse will be slow and flaky. Decide whether the test will move the clock, trigger the job explicitly through the management API, or run the executor in a controlled configuration. Whichever you choose, assert two things: that the timer job exists with the expected due date, and that after the job runs the process moved to the timer path. A timer that is created but never picked up is a common defect, so verify pickup rather than assuming it.
Error paths should assert two things together: that the catch activity is reached, and that no unexpected service task ran on the way. A boundary error event that routes to a compensation or notification step is easy to pass by checking only the final state, while a failed call that still executed a later delegate goes unnoticed. Force the error through the delegate or the variable the service task depends on, then assert the active activity after the catch and check that the downstream service tasks were never entered. Ask your team to confirm how error codes are thrown and matched in the Flowable version you run, since the matching rules differ between BPMN error definitions and generic exceptions.
Keeping test data, credentials and engine state safe
Flowable tests load real deployments, scripts, identity wiring and a database, so they can leak the same risks as production if they are set up carelessly. The cards below cover the data, identity, scripting, caching and environment choices that keep test runs contained and make failures easier to trace.
Keep production data out of test databases
Flowable's tests write process instances, variables, history and job rows to whatever database the configuration points at. Use an in-memory or disposable database such as H2 for unit tests, or a throwaway schema on a test server, and never a connection string copied from production. Before each run, confirm the JDBC URL in the test configuration resource, since a single overridden property can redirect the whole suite. Variables in test fixtures should be synthetic, not exported from real customer records.
Stub identity or use a real service deliberately
Flowable lets you replace identity management by overriding IdmIdentityServiceImpl or implementing the IdmIdentityService interface, and an LDAP identity service is provided as a reference. Unit tests for candidate groups and assignees are usually best served by a small in-test identity service with fixed users and groups. Connecting tests to a shared directory makes results depend on who changed that directory last. Confirm which identity service each test profile loads so that a stub is never silently replaced by a live LDAP binding.
Review scripts and XML in test resources
Script tasks, expressions and BPMN XML in test resources run with the same engine privileges as production definitions, so they carry the same scripting and XML risks. Copying a production model into test resources without review can bring unsafe script content along with it. Keep test-only models in a separate resource folder, apply the same code review rules to them, and avoid pasting untrusted XML from external sources into fixtures. Check that test scripts do not call network services or read local files unless the test explicitly needs that behavior.
Watch engine caching across tests
The Flowable engine caches deployed process definitions and related state, and a test that shares one engine instance can inherit definitions, flags or variables from an earlier test. Such leakage produces failures that depend on test order. Decide per test class whether to reuse an engine or rebuild it, and reset deployments and identity fixtures in setup or teardown. Ask your test lead to run the suite in a shuffled order at least once to expose hidden dependencies.
Keep test deployments out of shared environments
A test deployment must never reach a shared environment such as a staging database used by other teams or a pipeline that also deploys to production. Process definitions deployed to a shared engine become visible to every user and can start instances that trigger timers, service tasks or notifications. Scope test deployments to the in-test engine, use separate database names per pipeline stage, and fail the build if a test writes to a non-test URL. Record in the test plan which environments the suite is allowed to touch.
Questions teams ask before writing Flowable process tests
How much effort does a Flowable test suite take to start?
The starting effort is mostly set by how many processes you have and how much of each one is already testable. A first suite needs a test base class, a database the engine can reach, and a handful of deployed BPMN resources with a few tests per path. Most of the early work is deciding what counts as a unit: a single process, a subprocess, or a delegate on its own. Teams with clean delegates and small process models start faster than teams whose logic lives inside long scripts or hard-wired service calls. Ask any partner to break the effort into test harness setup, per-process coverage and CI wiring, so you can see which parts scale with your model count.
Can an in-house developer build these tests without outside help?
Yes, if the developer knows Java and JUnit and has read the Flowable testing approach, which uses the FlowableTest annotation and the FlowableExtension JUnit 5 extension. A developer who has never run a process engine will need time to understand deployments, the runtime service, and how the engine caches state between tests. Outside help is most useful for the first harness, for setting conventions the whole team will follow, and for covering gateways, timers and error paths that are easy to miss. You can also agree a scope in writing with a partner and then take over the suite yourself.
How long does setup usually run, and what sets that time?
Setup time depends on the number of process definitions, the complexity of their gateways, timers and boundary events, and how many external services the delegates call. Test doubles for those services are often the largest single item. Other drivers include whether your database configuration for tests matches production, how much existing logic must be moved out of scripts, and how quickly your team can review test designs. Because duration depends on your model and environment, ask for a timeline tied to a written list of processes in scope rather than a generic estimate.
What skills and infrastructure does the work need from our team?
The work needs three things. The first is Java skills sufficient to write JUnit 5 tests, configure Spring or plain Java bootstrapping, and reason about delegate classes. The second is BPMN familiarity, so testers can read the model and identify paths, gateway conditions, timer definitions and error boundaries that need coverage. The third is a test database. Flowable supports H2, MySQL, Oracle, PostgreSQL, DB2 and Microsoft SQL Server, and tests should run against a database type close to production, so confirm which one your team uses and how it is provisioned for CI. A product owner who can explain expected outcomes for each path also shortens the work.
How should tests be maintained when a process changes?
Treat each test as tied to a process path, not to an element ID alone. When a process changes, the tests for paths that did not change should keep passing, and the changed paths need updated assertions and possibly new cases. Keep deployment resources in version control next to the tests so each change is reviewed together. Gateway conditions and timer expressions deserve their own tests because small edits there cause the most silent failures. Agree on who updates tests when a model changes, and make test updates part of the same pull request as the model change.
What happens to the test suite when Flowable is upgraded?
Upgrades can change the test harness, not only the engine. Flowable 8.0.0 moved to Spring Framework 7 and Spring Boot 4, dropped support for Spring Boot 3, and removed JUnit 3 and 4 support, according to its release notes. Teams still on older test styles, or on a Spring Boot 3 baseline, will need to plan that change before or alongside the engine upgrade. Budget time for reviewing each test class against the new APIs, and confirm the upgrade path with your partner or in the release notes before you start. Keeping the harness small and well isolated reduces the number of files that change.
Does a test suite replace the need for production monitoring?
No. Tests verify that the process behaves as the model describes under the scenarios you wrote. They do not cover load, the behavior of real external systems, or failures that appear only in production data. Plan monitoring of failed jobs and timers separately, and use the tests to make sure the error paths you monitor for are actually exercised. Ask any partner which production checks sit outside the test scope, so both sides know where coverage stops.
How DeployOpen scopes and delivers Flowable test suites
DeployOpen plans, builds and hands over JUnit 5 test suites for Flowable processes inside your own codebase and build pipeline. Each engagement starts with a written scope that names the processes, gateways, timers and error paths to be covered, so the suite reflects how your models actually run.
How we scope
- Inventory the BPMN models, delegates and expressions that need unit coverage before work starts
- List the gateways, timer boundary events and error paths that each test class must exercise
- Agree in writing which Flowable engine version, database and Spring or plain Java setup the tests target
- Engagements are fixed-scope, with deliverables and timelines agreed in writing before any build begins
How we build
- Write FlowableTest and FlowableExtension test classes that deploy each model with @Deployment resources
- Set up configuration resources so the engine is built the same way in tests as in your application
- Structure engine caching across tests so shared state does not hide failures or slow the build
- Cover timers and error events with deterministic job execution and assertions on the resulting process state
How we hand over
- Deliver the test source, resource files and build configuration in your repository under your own review process
- Document how to run the suite locally and in CI, and which coverage gaps were agreed and left open
- Walk your engineers through adding a test for a new gateway, timer or delegate so the suite can grow in-house
- Record the Flowable version and test dependencies used, so later upgrades know what to recheck
How we support
- Ongoing support, upgrades and continuous testing are available as retainers, scoped separately from the build
- Review test failures after Flowable version changes and update the suite for changed engine behavior
- Confirm with your team which test ownership model fits, whether in-house, shared or retained with DeployOpen
- Ask for a written quote based on the agreed scope, which sets the cost of the build and any retainer

