How it works¶
The problem¶
A Step Functions development loop usually looks like: deploy the state machine to AWS, start an execution, read the execution history, find what broke, change the definition or a Lambda/Batch job, redeploy, run again. Every iteration costs minutes and money.
AWS’s test_state
API helps for a single state: given a state definition and an input, it evaluates the state’s
data flow (InputPath / Parameters / Arguments / ResultSelector / Output) and tells you the next
state — without deploying anything. But it has two limits:
It runs one state at a time — it doesn’t walk a whole machine.
It cannot execute service integrations such as
.sync(e.g.arn:aws:states:::batch:submitJob.sync), nestedstartExecution.sync:2, or.waitForTaskToken— the exact steps that are slowest to iterate on.
This package grew out of a data pipeline built almost entirely from batch:submitJob.sync
steps: every one needed a hand-rolled workaround to test, and running the real state machine on
AWS to validate a change was far too slow. The aim was a quick way to run the whole pipeline
end-to-end locally, with the Batch steps actually executing in local containers.
The approach¶
The real Step Functions console graph of the docker-batch example,
annotated to show how each state is handled. Editable source:
overview.drawio (the console graph is embedded; edit the annotation layer in
draw.io and re-export overview.svg).
WorkflowRunner walks your real ASL definition state-by-state. For each state it:
Picks a strategy that supplies the state’s result (see Selecting how each step runs). States you don’t map are handled automatically.
Calls
test_statewith your definition + that result, so AWS still does the engine work — the data transforms and the next-state transition. You get faithful behavior, not a reimplementation of the States Language.Feeds the resulting output into the next state, and repeats until the machine ends.
┌───────────────────────── WorkflowRunner.run loop ─────────────────────────┐
input ──▶ │ pick strategy ─▶ strategy.execute() ─▶ result │
│ │ │
│ ▼ │
│ test_state(definition, input, **result) ◀── AWS evaluates data flow │
│ │ + returns nextState │
│ ▼ │
│ output ──────────────────────────▶ next state ─┐ │
└────────────────────────────────────────────────────────────┘ │
(loop)
For a step the API can’t run — say a batch:submitJob.sync task — a strategy like
DockerBatchStrategy builds and runs that step’s container locally and returns its output;
test_state then applies the state’s Output/ResultSelector exactly as it would in production.
What you provide¶
role_arn— an IAM role/credentials allowed to calltest_state.asl_registry— your ASL definition(s), keyed by name; the entry point must be"main". Nested state machines are registered too (see Control flow).mock_mapping—{state_name: strategy}choosing how the steps that need help run.Optional
variables(Step Functions context variables), aninput_validation_function, and aregion(otherwise resolved fromAWS_REGION/ your AWS config).
Then runner.start(initial_input) returns the final output. See
Getting started / usage for a complete worked example.