Testing processes
A process test checks that your process model does what you expect. It deploys your BPMN, DMN and forms into a real engine and starts a process instance. It then drives the instance forward (completing jobs, moving time, completing user tasks) and checks what the engine recorded.
TinyConductor runs these tests on the Embedded engine. It is the same engine as Bundled and Cluster mode, but it keeps nothing on disk, needs no network and has a virtual clock that you control.
Why Embedded mode
Section titled “Why Embedded mode”- Same behaviour. The path an instance takes, the records, incidents, FEEL and DMN are the same as in the production modes. The modes differ only in durability, speed, capacity and tenancy (see Embedded mode).
- Nothing else to install. No database and no volume. An engine is created in milliseconds and thrown away with everything it held.
- A virtual clock. Time starts at a fixed epoch (
2026-07-19T00:00:00Z) and stands still until the test moves it. - Deterministic. Calls are processed one at a time, in order, so the same test produces the same records every run.
The virtual clock
Section titled “The virtual clock”The clock never moves on its own. You advance it with a single call:
POST /v1/embedded/clock/advance-by with {"milliseconds": 86400000}, or
advance-to with {"instant": "2026-08-01T00:00:00Z"}.
The call returns only when every timer due at the new instant has fired and all the work it caused is in the record stream (jobs created, messages expired, further engine steps). Your test reads the result straight after the call. It never sleeps and never polls.
| Operation | Server form | Answer |
|---|---|---|
| read the clock | GET /v1/embedded/clock | {"mode":"embedded","frozen":true,"instant":…,"epochMillis":…} |
| advance by a duration | POST /v1/embedded/clock/advance-by {"milliseconds": n} | the new clock |
| advance to an instant | POST /v1/embedded/clock/advance-to {"instant": "<RFC 3339>"} | the new clock; 400 invalid-argument if it is in the past |
| start over | POST /v1/embedded/reset | drops every deployment, instance, record and document, and returns the clock to the epoch |
The clock only moves forward. The v2-shaped PUT /v2/clock and
POST /v2/clock/reset drive the same clock (see
Embedded mode).
Assert on records
Section titled “Assert on records”A record is one entry in the engine’s log of what happened, such as “a
job was created” or “an element completed”. Everything the engine did is in
this record stream, in order. GET /v1/embedded/records answers
{"records": [...]}. Each record carries
position, key, timestamp (microseconds), recordType, valueType
(PROCESS_INSTANCE, JOB, USER_TASK, VARIABLE, TIMER, DECISION_EVALUATION, …),
intent and a value. For example, the instance itself completed when a
record has valueType PROCESS_INSTANCE, intent ELEMENT_COMPLETED, and a
value whose elementId is the process id and whose processInstanceKey is
your instance:
{"position": 58, "key": "94575592174780421", "timestamp": 1784505600000000, "recordType": "EVENT", "valueType": "PROCESS_INSTANCE", "intent": "ELEMENT_COMPLETED", "value": {"bpmnProcessId": "order-process", "processInstanceKey": "94575592174780421", "elementId": "order-process", "bpmnElementType": "PROCESS", …}}Keys are JSON strings on the wire, so they survive languages whose numbers
lose precision above 2^53. The same records answer “which elements
completed”, “which variables were set” and “was an incident raised”. The
history reads (GET /api/v1/history/process-instances/{key}) and the searches
work in Embedded mode too, if you prefer a summarized view.
Give every test a clean engine: create a new in-process engine per test, or
call POST /v1/embedded/reset before each test against a server.
Two forms: in-process and server
Section titled “Two forms: in-process and server”| In-process | Server | |
|---|---|---|
| What runs | the engine inside your test process, as a library | tinyconductor --mode embedded, one process your tests call over HTTP |
| Languages | .NET (SDK), C and any language with a C FFI; a Rust crate is planned | any language with an HTTP client, Rust included |
| Get it | the library for your language (see each guide) | the registry.tinyfactory.ai/tinyblox/tinyconductor container image, or the tinyconductor program from a release archive |
| API | typed SDK, or JSON operations through the C ABI | the REST API — the same routes every mode serves, plus /v1/embedded/* |
| Isolation | one engine per test | POST /v1/embedded/reset between tests |
Start the server form from the container image:
export TINYCONDUCTOR_TOKEN=test-tokendocker run --rm -d --name tinyconductor-test -p 127.0.0.1:8080:8080 \ -e CONDUCTOR_AUTH_CREDENTIALS_JSON='[{"token":"test-token","subject":"tests","tenantId":"default","kind":"service","actions":["*"]}]' \ registry.tinyfactory.ai/tinyblox/tinyconductor:0.1.0 --mode embeddedA container engine listens beyond loopback, so it needs a credential; the
tests send the token from TINYCONDUCTOR_TOKEN as Authorization: Bearer ….
Or run the tinyconductor program from the release archive for your platform
(the list of downloads is in Embedded mode):
./tinyconductor-0.1.0-linux-x86_64/tinyconductor --mode embeddedStarted this way it listens on 127.0.0.1:8080 and answers local callers
without a credential. In a pipeline, see CI recipes.
Guides by language
Section titled “Guides by language”Every guide walks through the same test of the shared order sample
(order.bpmn): deploy, start an order, complete the charge-card
job, advance the clock past the 24-hour review timer, complete
send-invoice, complete the confirm-shipment user task, and assert that
the instance completed. Each has a runnable project in the
examples download, under testing/.
| Language | Form | Guide | Example |
|---|---|---|---|
| C# / .NET | in-process SDK | .NET | testing/dotnet |
| Rust | server + REST (in-process crate planned) | Rust | testing/rust |
| C, and any FFI language | in-process C ABI | C and FFI | testing/c |
| Java | server + REST | Java | testing/java |
| TypeScript / Node.js | server + REST | TypeScript | testing/typescript |
| Python | server + REST | Python | testing/python |
| Go | server + REST | Go | testing/go |
Starting the engine in a pipeline: CI recipes.
The examples download
Section titled “The examples download”The sample order process and the runnable projects are in the examples
download of the release, tinyconductor-0.1.0-examples.tar.gz, next to the
engine downloads (where to download them is given with your access). Unpack
it:
tar -xzf tinyconductor-0.1.0-examples.tar.gzThe unpacked tinyconductor-0.1.0-examples directory holds order.bpmn,
order.form and order-decisions.dmn at the top, and one project per
language under testing/. Each project reads the sample files from the top
directory, so keep the layout as it is.
The examples need nothing but release files. The C and .NET projects look
for the release archive for your platform unpacked next to the examples, and
the .NET project for the TinyConductor.Testing package file, so put the
downloads in one folder:
downloads/ tinyconductor-0.1.0-examples/ (the examples download, unpacked) tinyconductor-0.1.0-linux-x86_64/ (the release archive for your platform, unpacked) TinyConductor.Testing.0.1.0.nupkg (the .NET testing SDK, for .NET only)