Integration Testing
The primary use case for this emulator is integration testing. You define workflows in YAML, run them against your local services, and verify the orchestration works correctly.
Pattern
- Start the emulator (as a separate process or in
TestMain) - Deploy a workflow via the REST API
- Execute it
- Poll for completion
- Assert on the result
Go test example
package myservice_test
import (
"bytes"
"encoding/json"
"net/http"
"os"
"testing"
"time"
)
var emulatorURL string
func TestMain(m *testing.M) {
emulatorURL = os.Getenv("WORKFLOWS_EMULATOR_HOST")
if emulatorURL == "" {
emulatorURL = "http://localhost:8787"
}
os.Exit(m.Run())
}
func deployWorkflow(t *testing.T, id, source string) {
t.Helper()
body, _ := json.Marshal(map[string]string{"sourceContents": source})
url := emulatorURL + "/v1/projects/my-project/locations/us-central1/workflows?workflowId=" + id
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("deploy failed: %d", resp.StatusCode)
}
}
func runWorkflow(t *testing.T, id string, args map[string]interface{}) map[string]interface{} {
t.Helper()
body := map[string]interface{}{}
if args != nil {
argsJSON, _ := json.Marshal(args)
body["argument"] = string(argsJSON)
}
data, _ := json.Marshal(body)
url := emulatorURL + "/v1/projects/my-project/locations/us-central1/workflows/" + id + "/executions"
resp, err := http.Post(url, "application/json", bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
var exec map[string]interface{}
json.NewDecoder(resp.Body).Decode(&exec)
resp.Body.Close()
// Poll for completion
name := exec["name"].(string)
getURL := emulatorURL + "/v1/" + name
for i := 0; i < 100; i++ {
resp, _ = http.Get(getURL)
json.NewDecoder(resp.Body).Decode(&exec)
resp.Body.Close()
state := exec["state"].(string)
if state == "SUCCEEDED" || state == "FAILED" || state == "CANCELLED" {
return exec
}
time.Sleep(100 * time.Millisecond)
}
t.Fatal("execution timed out")
return nil
}
func TestOrderWorkflow(t *testing.T) {
deployWorkflow(t, "order-flow", `
main:
params: [args]
steps:
- validate:
call: http.post
args:
url: http://localhost:9090/api/validate
body:
order_id: ${args.order_id}
result: resp
- done:
return: ${resp.body}
`)
result := runWorkflow(t, "order-flow", map[string]interface{}{
"order_id": "ORD-123",
})
if result["state"] != "SUCCEEDED" {
t.Fatalf("expected SUCCEEDED, got %s: %v", result["state"], result["error"])
}
}
Running tests
Start the emulator in one terminal:
go run ./cmd/gcw-emulator
Run your tests in another:
WORKFLOWS_EMULATOR_HOST=http://localhost:8787 go test -v ./...
Tips
- Use unique workflow IDs per test (e.g., append a timestamp) to avoid conflicts when running tests in parallel
- The emulator runs executions asynchronously -- always poll
GET .../executions/{id}until you see a terminal state - For tests that expect failures, check
exec["state"] == "FAILED"and inspectexec["error"] - The
argumentfield in create-execution is a JSON string, not a JSON object. Marshal your args to JSON and pass as a string.