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:

  1. It runs one state at a time — it doesn’t walk a whole machine.

  2. It cannot execute service integrations such as .sync (e.g. arn:aws:states:::batch:submitJob.sync), nested startExecution.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

Annotated Step Functions workflow showing how the toolkit runs each state type: test_state for engine logic, a strategy (e.g. local Docker) for .sync steps, and recursion for nested subflows

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:

  1. Picks a strategy that supplies the state’s result (see Selecting how each step runs). States you don’t map are handled automatically.

  2. Calls test_state with 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.

  3. 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 call test_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), an input_validation_function, and a region (otherwise resolved from AWS_REGION / your AWS config).

Then runner.start(initial_input) returns the final output. See Getting started / usage for a complete worked example.