Skip to content

Flow testing

Pipebase ships a built-in flow-test framework — a MUnit-class given/when/then surface for Camel flows, with connector mocking. It requires no separate test DSL beyond one YAML file per flow, and runs the same way locally, in the designer, and in CI.

A flow test asserts against a real deployed flow running on a real Camel runtime — not a code-level unit test of a route builder, and not a fully mocked simulation. Under the hood:

  1. The flow-under-test deploys to a runtime (ephemeral, or one you point at).
  2. A test session opens on that runtime and arms one or more step mocks — “when step X runs, short-circuit it and return this body / throw this exception” instead of letting the real connector fire.
  3. The trigger fires (an HTTP request, or a message produced to a direct: endpoint).
  4. Assertions run against the HTTP response, specific steps’ captured in/out bodies, and how the mocked step(s) were called.

Because both the mock and the assertions ride the runtime’s real trace machinery, a green flow test is strong evidence the flow works end-to-end minus whatever you explicitly mocked — typically outbound connectors you don’t want to hit in CI, like an inventory API or a payment gateway.

A suite is one YAML file with a list of tests. Each test has three parts:

version: 1
tests:
- id: t_reserve_ok
name: reserves stock via the (mocked) inventory API
given:
mocks:
- step: r0_s2
nodeId: r0_s2
respond:
body: '{"reservationId":"R1"}'
headers:
Content-Type: application/json
when:
kind: http
method: POST
path: /orders
body: SKU-123
headers:
Content-Type: text/plain
then:
response:
status: 200
bodyContains: R1
steps:
- step: r0_s3
out:
bodyContains: reserved SKU-123
mockCalled:
- step: r0_s2
times: 1
withBodyContains: SKU-123
- id: t_inventory_down
name: surfaces a 500 when the inventory connector fails
given:
mocks:
- step: r0_s2
throw:
type: java.io.IOException
message: inventory down
when:
kind: http
method: POST
path: /orders
body: SKU-9
headers:
Content-Type: text/plain
then:
response:
status: 500
  • given.mocks[] arms step mocks before the trigger fires. Each mock targets a Camel step id and either responds with a body/headers or throws a configured exception.
  • when fires the trigger — kind: http sends a real request to the flow’s HTTP trigger; kind: produce sends to a direct: endpoint for flows triggered internally rather than over HTTP.
  • then asserts on the HTTP response, per-steps captured bodies, and mockCalled (did the mocked step get invoked, how many times, with what body). All body assertions support bodyEquals, bodyContains, bodyJsonPath, and headerEquals.

The next-editor (/edit-next/:id) has a Tests drawer. It lets you add a test, name it, pick the trigger kind, and mock/assert steps by pointing at a node on the canvas rather than typing a raw Camel step id — a dropdown lists every node with a stable step id, labelled by its display name. Running the suite from the drawer shows pass/fail per test, and a failing test highlights the involved nodes on the canvas in red so you can jump straight from “test failed” to “here’s the responsible node.”

Saving in the drawer writes the canonical test store and re-exports a human-readable flow.tests.yaml next to the flow’s Camel YAML — the format shown above, meant for git and code review.

Terminal window
pipebase-test --all --flows-dir <dir> --runtime ephemeral --junit out.xml
FlagMeaning
<flowId> (positional)Run just this flow’s suite.
--allRun every flow under --flows-dir that has a test suite.
--flows-dir <dir>Data directory to scan.
--runtime ephemeral | <url>ephemeral spins up a throwaway container and tears it down afterward; a URL runs against an already-running runtime.
--junit <path>Write a JUnit XML report for CI test-result UIs.

Exit code is non-zero if any suite has a failed test. A bundled orders-api example ships under designer/examples/flow-tests/ and runs in the project’s own CI leg on every push.

  • Trigger kinds: only http and produce (direct:) triggers are supported today. Timer- or queue-triggered flows can’t be fired by a flow test yet.
  • One deploy per suite. All tests in a suite share a single deployment — if a test mutates state a later test depends on (an in-process cache, say), that state can bleed between tests. Order tests defensively.
  • match is substring-only. Mock/assertion body matching has no regex or JSONPath wildcards beyond the explicit bodyJsonPath field.
  • Only HTTP-transport runtimes are supported. Pipe-mode (tunnel) runtimes don’t currently support test sessions — point --runtime at an inbound runtime URL or use the ephemeral local one.