Step Functions

Get started with Step Functions on LocalStack

Introduction

Step Functions is a serverless workflow engine that enables the orchestrating of multiple AWS services. It provides a JSON-based structured language called Amazon States Language (ASL) which allows to specify how to manage a sequence of tasks and actions that compose the application’s workflow. Thus making it easier to build and maintain complex and distributed applications.

LocalStack allows you to use the Step Functions APIs in your local environment to create, execute, update, and delete state machines locally. The supported APIs are available on our API coverage page, which provides information on the extent of Step Function’s integration with LocalStack.

Getting started

This guide is designed for users new to Step Functions and assumes basic knowledge of the AWS CLI and our awslocal wrapper script.

Start your LocalStack container using your preferred method. We will demonstrate how you can create a state machine, execute it, and check the status of the execution.

Create a state machine

You can create a state machine using the CreateStateMachine API. The API requires the name of the state machine, the state machine definition, and the role ARN that the state machine will assume to call AWS services. Run the following command to create a state machine:

$ awslocal stepfunctions create-state-machine \
    --name "CreateAndListBuckets" \
    --definition '{
        "Comment": "Create bucket and list buckets",
        "StartAt": "CreateBucket",
            "States": {
            "CreateBucket": {
                "Type": "Task",
                "Resource": "arn:aws:states:::aws-sdk:s3:createBucket",
                "Parameters": {
                    "Bucket": "new-sfn-bucket"
                },
                "Next": "ListBuckets"
            },
            "ListBuckets": {
                "Type": "Task",
                "Resource": "arn:aws:states:::aws-sdk:s3:listBuckets",
                "End": true
            }
        }
    }' \
    --role-arn "arn:aws:iam::000000000000:role/stepfunctions-role"

The output of the above command is the ARN of the state machine:

{
    "stateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:CreateAndListBuckets",
    "creationDate": 1714643996.18017
}

Execute the state machine

You can execute the state machine using the StartExecution API. The API requires the state machine’s ARN and the state machine’s input. Run the following command to execute the state machine:

$ awslocal stepfunctions start-execution \
    --state-machine-arn "arn:aws:states:us-east-1:000000000000:stateMachine:CreateAndListBuckets"

The output of the above command is the execution ARN:

{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:CreateAndListBuckets:bf7d2138-e96f-42d1-b1f9-41f0c1c7bc3e",
    "startDate": 1714644089.748442
}

Check the execution status

To check the status of the execution, you can use the DescribeExecution API. Run the following command to describe the execution:

$ awslocal stepfunctions describe-execution \
        --execution-arn "arn:aws:states:us-east-1:000000000000:execution:CreateAndListBuckets:bf7d2138-e96f-42d1-b1f9-41f0c1c7bc3e"

Replace the execution-arn with the ARN of the execution you want to describe.

The output of the above command is the execution status:

{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:CreateAndListBuckets:bf7d2138-e96f-42d1-b1f9-41f0c1c7bc3e",
    "stateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:CreateAndListBuckets",
    "name": "bf7d2138-e96f-42d1-b1f9-41f0c1c7bc3e",
    "status": "SUCCEEDED",
    "startDate": 1714644089.748442,
    "stopDate": 1714644089.907964,
    "input": "{}",
    "inputDetails": {
        "included": true
    },
    "output": "{\"Buckets\":[{\"Name\":\"cdk-hnb659fds-assets-000000000000-us-east-1\",\"CreationDate\":\"2024-05-02T09:53:54+00:00\"},{\"Name\":\"new-sfn-bucket\",\"CreationDate\":\"2024-05-02T10:01:29+00:00\"}],\"Owner\":{\"DisplayName\":\"webfile\",\"Id\":\"75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a\"}}",
    "outputDetails": {
        "included": true
    }
}

Supported services and operations

Step Functions integrates with AWS services, allowing you to invoke API actions for each service within your workflow. LocalStack’s Step Functions emulation supports the following AWS services:

Supported service integrationsServiceRequest ResponseRun a Job (.sync)Run a Job (.sync2)Wait for Callback (.waitForTaskToken)
Optimized integrationsLambda
DynamoDB
Amazon ECS/AWS Fargate
Amazon SNS  
Amazon SQS
API Gateway
Amazon EventBridge
AWS Glue
AWS Step Functions
AWS Batch
AWS SDK integrationsAll LocalStack services

Mocked Service Integrations

Mocked service integrations let you test AWS Step Functions without invoking LocalStack’s emulated AWS services.
Instead, Task states return predefined outputs from a mock configuration file.

The key components are:

  • Mocked service integrations: Task states that return predefined responses instead of calling local AWS services.
  • Mocked responses: Static payloads linked to mocked Task states.
  • Test cases: Executions of your state machine that use mocked responses.
  • Mock configuration file: A JSON file that defines test cases, mocked states, and their response payloads.

During execution, each Task state listed in the mock file returns its associated mocked response. States not included in the file continue to invoke the corresponding emulated services, allowing a mix of mocked and real interactions.

You can define one or more mocked payloads per Task state.

Supported integration patterns include .sync, .sync2, and .waitForTaskToken.

Both success and failure scenarios can be simulated.

Compatibility with AWS Step Functions Local

LocalStack can also serve as a drop-in replacement for AWS Step Functions Local testing with mocked service integrations. It supports test cases with mocked Task states and maintains compatibility with existing Step Functions Local configurations. This functionality is extended in LocalStack by providing access to the latest Step Functions features such as JSONata and Variables, as well as the ability to enable both mocked and emulated service interactions emulated by LocalStack.

Identify a State Machine for Mocked Integrations

Mocked service integrations apply to specific state machine definitions. The first step is to select the state machine where mocked responses should be applied.

In this example, we’ll use a state machine named LambdaSQSIntegration, defined as follows:

{
  "Comment": "This state machine is called: LambdaSQSIntegration",
  "QueryLanguage": "JSONata",
  "StartAt": "LambdaState",
  "States": {
    "LambdaState": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Arguments": {
        "FunctionName": "GreetingsFunction",
        "Payload": {
          "fullname": "{% $states.input.name & ' ' & $states.input.surname %}"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [ "States.ALL" ],
          "IntervalSeconds": 2,
          "MaxAttempts": 4,
          "BackoffRate": 2
        }
      ],
      "Assign": {
        "greeting": "{% $states.result.Payload.greeting %}"
      },
      "Next": "SQSState"
    },
    "SQSState": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sqs:sendMessage",
      "Arguments": {
        "QueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/localstack-queue",
        "MessageBody": "{% $greeting %}"
      },
      "End": true
    }
  }
}

Define Mock Integrations in a Configuration File

Mock integrations are defined in a JSON file that follows the RawMockConfig schema.

This file contains two top-level sections:

  • StateMachines – Maps each state machine to its test cases, specifying which states use which mocked responses.
  • MockedResponses – Defines reusable mock payloads, each identified by a ResponseID, which test cases can reference.

StateMachines

This section specifies the Step Functions state machines to mock, along with their corresponding test cases.

Each test case maps state names to ResponseIDs defined in the MockedResponses section.

"StateMachines": {
  "<StateMachineName>": {
    "TestCases": {
      "<TestCaseName>": {
        "<StateName>": "<ResponseID>",
        ...
      }
    }
  }
}

In the example above:

  • StateMachineName: Must exactly match the name used when the state machine was created in LocalStack.
  • TestCases: Named scenarios that define mocked behavior for specific Task states.

Each test case maps Task states to mock responses that define their expected behavior.

At runtime, if a test case is selected, the state uses the mocked response (if defined); otherwise, it falls back to calling the emulated service.

Below is a complete example of the StateMachines section:

"LambdaSQSIntegration": {
  "TestCases": {
    "LambdaRetryCase": {
      "LambdaState": "MockedLambdaStateRetry",
      "SQSState":   "MockedSQSStateSuccess"
    }
  }
}

MockedResponses

This section defines mocked responses for Task states.

Each ResponseID includes one or more step keys and defines either a Return value or a Throw error.

"MockedResponses": {
  "<ResponseID>": {
    "<step-key>": { "Return": ... },
    "<step-key>": { "Throw": ... }
  }
}

In the example above:

  • ResponseID: A unique identifier used in test cases to reference a specific mock response.
  • step-key: Indicates the attempt number. For example, "0" refers to the first try, while "1-2" covers a range of attempts.
  • Return: Simulates a successful response by returning a predefined payload.
  • Throw: Simulates a failure by returning an Error and an optional Cause.

Here is a complete example of the MockedResponses section:

"MockedLambdaStateRetry": {
  "0": {
    "Throw": {
      "Error": "Lambda.ServiceException",
      "Cause": "An internal service error occurred."
    }
  },
  "1-2": {
    "Throw": {
      "Error": "Lambda.TooManyRequestsException",
      "Cause": "Invocation rate limit exceeded."
    }
  },
  "3": {
    "Return": {
      "StatusCode": 200,
      "Payload": {
        "greeting": "Hello John Smith, you’re now testing mocked integrations with LocalStack!"
      }
    }
  }
}

The MockConfigFile.json below is used to test the LambdaSQSIntegration state machine defined earlier.

{
  "StateMachines":{
    "LambdaSQSIntegration":{
      "TestCases":{
        "BaseCase":{
          "LambdaState":"MockedLambdaStateSuccess",
          "SQSState":"MockedSQSStateSuccess"
        },
        "LambdaRetryCase":{
          "LambdaState":"MockedLambdaStateRetry",
          "SQSState":"MockedSQSStateSuccess"
        },
        "HybridCase":{
          "LambdaState":"MockedLambdaSuccess"
        }
      }
    }
  },
  "MockedResponses":{
    "MockedLambdaStateSuccess":{
      "0":{
        "Return":{
          "StatusCode":200,
          "Payload":{
            "greeting":"Hello John Smith, you’re now testing mocked integrations with LocalStack!"
          }
        }
      }
    },
    "MockedSQSStateSuccess":{
      "0":{
        "Return":{
          "MD5OfMessageBody":"3661896f-1287-45a3-8f89-53bd7b25a9a6",
          "MessageId":"7c9ef661-c455-4779-a9c2-278531e231c2"
        }
      }
    },
    "MockedLambdaStateRetry":{
      "0":{
        "Throw":{
          "Error":"Lambda.ServiceException",
          "Cause":"An internal service error occurred."
        }
      },
      "1-2":{
        "Throw":{
          "Error":"Lambda.TooManyRequestsException",
          "Cause":"Invocation rate limit exceeded."
        }
      },
      "3":{
        "Return":{
          "StatusCode":200,
          "Payload":{
            "greeting":"Hello John Smith, you’re now testing mocked integrations with LocalStack!"
          }
        }
      }
    }
  }
}

Provide the Mock Configuration to LocalStack

Set the SFN_MOCK_CONFIG environment variable to the path of your mock configuration file.

If you’re running LocalStack in Docker, mount the file and pass the variable as shown below:

LOCALSTACK_SFN_MOCK_CONFIG=/tmp/MockConfigFile.json \
localstack start --volume /path/to/MockConfigFile.json:/tmp/MockConfigFile.json
services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566"            # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      # LocalStack configuration: https://docs.localstack.cloud/references/configuration/
      - DEBUG=${DEBUG:-0}
      - SFN_MOCK_CONFIG=/tmp/MockConfigFile.json
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./MockConfigFile.json:/tmp/MockConfigFile.json"

Run Test Cases with Mocked Integrations

Create the state machine to match the name defined in the mock configuration file.

In this example, create the LambdaSQSIntegration state machine using:

$ awslocal stepfunctions create-state-machine \
    --definition file://LambdaSQSIntegration.json \
    --name "LambdaSQSIntegration" \
    --role-arn "arn:aws:iam::000000000000:role/service-role/testrole"

After the state machine is created and correctly named, you can run test cases defined in the mock configuration file using the StartExecution API.

To execute a test case, append the test case name to the state machine ARN using #.

This tells LocalStack to apply the corresponding mocked responses from the configuration file.

For example, to run the BaseCase test case:

$ awslocal stepfunctions start-execution \
    --state-machine arn:aws:states:us-east-1:000000000000:stateMachine:LambdaSQSIntegration#BaseCase \
    --input '{"name": "John", "surname": "smith"}' \
    --name "MockExecutionBaseCase"

During execution, any state mapped in the mock config will use the predefined response.
States without mock entries invoke the actual emulated service as usual.

You can inspect the execution using the DescribeExecution API:

$ awslocal stepfunctions describe-execution \
    --execution-arn "arn:aws:states:us-east-1:000000000000:execution:LambdaSQSIntegration:MockExecutionBaseCase"

The sample output shows the execution details, including the state machine ARN, execution ARN, status, start and stop dates, input, and output:

{
    "executionArn": "arn:aws:states:us-east-1:000000000000:execution:LambdaSQSIntegration:MockExecutionBaseCase",
    "stateMachineArn": "arn:aws:states:us-east-1:000000000000:stateMachine:LambdaSQSIntegration",
    "name": "MockExecutionBaseCase",
    "status": "SUCCEEDED",
    "startDate": "...",
    "stopDate": "...",
    "input": "{\"name\":\"John\",\"surname\":\"smith\"}",
    "inputDetails": {
        "included": true
    },
    "output": "{\"MessageId\":\"7c9ef661-c455-4779-a9c2-278531e231c2\",\"MD5OfMessageBody\":\"3661896f-1287-45a3-8f89-53bd7b25a9a6\"}",
    "outputDetails": {
        "included": true
    }
}

You can also use the GetExecutionHistory API to retrieve the execution history, including the events and their details.

$ awslocal stepfunctions get-execution-history \
    --execution-arn "arn:aws:states:us-east-1:000000000000:execution:LambdaSQSIntegration:MockExecutionBaseCase"

This will return the full execution history, including entries that indicate how mocked responses were applied to Lambda and SQS states.

...
{
    "timestamp": "...",
    "type": "TaskSucceeded",
    "id": 5,
    "previousEventId": 4,
    "taskSucceededEventDetails": {
        "resourceType": "lambda",
        "resource": "invoke",
        "output": "{\"StatusCode\": 200, \"Payload\": {\"greeting\": \"Hello John Smith, you\\u2019re now testing mocked integrations with LocalStack!\"}}",
        "outputDetails": {
            "truncated": false
        }
    }
}
...
{
    "timestamp": "...",
    "type": "TaskSucceeded",
    "id": 10,
    "previousEventId": 9,
    "taskSucceededEventDetails": {
        "resourceType": "sqs",
        "resource": "sendMessage",
        "output": "{\"MessageId\": \"7c9ef661-c455-4779-a9c2-278531e231c2\", \"MD5OfMessageBody\": \"3661896f-1287-45a3-8f89-53bd7b25a9a6\"}",
        "outputDetails": {
            "truncated": false
        }
    }
}
...

Resource Browser

The LocalStack Web Application includes a Resource Browser for managing Step Functions state machines.

To access it, open the LocalStack Web UI in your browser, navigate to the Resource Browser section, and click Step Functions under App Integration.

Step Functions Resource Browser

The Resource Browser allows you to perform the following actions:

  • Create state machine: Create a new state machine by clicking on the Create state machine button and providing the required information.
  • View state machine details: Click on a state machine to view its details, including the state executions, definition details, such as the schema and flowchart, and the state machine’s ARN.
  • Start execution: Start a new execution of the state machine by clicking on the Start Execution button and providing the input data.
  • Delete state machine: Delete a state machine by selecting it and clicking on the Actions button followed by Remove Selected button.

Examples

The following code snippets and sample applications provide practical examples of how to use Step Functions in LocalStack for various use cases: