Step Functions
10 minute read
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 integrations | Service | Request Response | Run a Job (.sync) | Run a Job (.sync2) | Wait for Callback (.waitForTaskToken) |
---|---|---|---|---|---|
Optimized integrations | Lambda | ✓ | ✓ | ||
DynamoDB | ✓ | ||||
Amazon ECS/AWS Fargate | ✓ | ✓ | ✓ | ||
Amazon SNS | ✓ | ✓ | |||
Amazon SQS | ✓ | ✓ | |||
API Gateway | ✓ | ✓ | |||
Amazon EventBridge | ✓ | ✓ | |||
AWS Glue | ✓ | ✓ | |||
AWS Step Functions | ✓ | ✓ | ✓ | ✓ | |
AWS Batch | ✓ | ✓ | |||
AWS SDK integrations | All 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.
Note
LocalStack does not validate response formats. Ensure the payload structure in the mocked responses matches what the real service expects.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 ResponseID
s 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 specificTask
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 anError
and an optionalCause
.
Note
Each entry must have eitherReturn
or Throw
, but cannot have both.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.

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: