Skip to content

Testing with Go

Go tests use the Embedded engine’s server form over the REST API, with nothing beyond the standard library (net/http, mime/multipart, encoding/json, testing).

The runnable project is testing/go in the examples download.

From the container image:

Terminal window
export TINYCONDUCTOR_TOKEN=test-token
docker 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 embedded

Or from the release archive for your platform (downloads are listed in Embedded mode); on loopback it needs no credential:

Terminal window
./tinyconductor-0.1.0-linux-x86_64/tinyconductor --mode embedded

The test reads TINYCONDUCTOR_URL (default http://127.0.0.1:8080), the optional TINYCONDUCTOR_TOKEN, and TINYCONDUCTOR_EXAMPLES (where the sample resources are, default ../..). The REST calls are listed in the Java guide.

func TestOrderCompletesAfterTheReviewPeriod(t *testing.T) {
// Every test starts from an empty engine at the fixed epoch.
call(t, "POST", "/v1/embedded/reset", nil)
deploy(t, "order.form", "application/json")
deploy(t, "order-decisions.dmn", "application/xml")
deploy(t, "order.bpmn", "application/xml")
key := call(t, "POST", "/v1/process-instances", map[string]any{
"processDefinitionId": "order-process",
"variables": map[string]any{"orderId": 4711, "amount": 99.9},
})["processInstanceKey"].(string) // keys are JSON strings
completeJob(t, "charge-card", map[string]any{"charged": true})
// The 24-hour review timer fires inside this call; no sleeping or polling.
call(t, "POST", "/v1/embedded/clock/advance-by", map[string]any{"milliseconds": 86_400_000})
completeJob(t, "send-invoice", map[string]any{})
tasks := call(t, "POST", "/v1/user-tasks/search", map[string]any{"processInstanceKey": key})["items"].([]any)
task := tasks[0].(map[string]any)
call(t, "POST", fmt.Sprintf("/v1/user-tasks/%s/completion", task["userTaskKey"]),
map[string]any{"variables": map[string]any{"shipped": true}})
completed := map[string]bool{}
for _, entry := range call(t, "GET", "/v1/embedded/records", nil)["records"].([]any) {
record := entry.(map[string]any)
value := record["value"].(map[string]any)
if record["valueType"] == "PROCESS_INSTANCE" && record["intent"] == "ELEMENT_COMPLETED" &&
value["processInstanceKey"] == key {
completed[value["elementId"].(string)] = true
}
}
for _, element := range []string{"order-process", "charge-card", "send-invoice", "confirm-shipment"} {
if !completed[element] {
t.Errorf("%s did not complete", element)
}
}
}

call, deploy and completeJob are small helpers in order_process_test.go. Run it:

Terminal window
cd tinyconductor-0.1.0-examples/testing/go
go test -v ./...

The example was verified in a golang:1.24 container against an Embedded engine on the host that was started with CONDUCTOR_AUTH_CREDENTIALS_JSON and a 0.0.0.0 bind, with TINYCONDUCTOR_URL pointing at the host and TINYCONDUCTOR_TOKEN set — the shape a containerized CI job uses (see CI recipes).

Tests in one package run one after another unless they call t.Parallel(); don’t, when they share one engine, since each begins with a reset.