AWS Step Function Localでステートマシンを分離環境でテストする(Mock Responseを使う場合)

2022.07.26

こんにちは、CX事業本部 IoT事業部の若槻です。

前回の下記エントリでは、AWS Step Function LocalをMock Responseを使わずに試してみました。

今回は、AWS Step Function LocalでMock Responseを使った場合のテストを試してみました。

やってみた

準備

テスト対象のState MachineのASL(Amazon States Language)定義のファイルです。コンテナ上にState Machineを作成する際に読み込ませます。

MyStateMachine.asl.json

{
  "Comment": "A description of my state machine",
  "StartAt": "GetParameter",
  "States": {
    "GetParameter": {
      "Type": "Task",
      "Next": "Choice",
      "Parameters": {
        "Name": "hoge"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter",
      "ResultSelector": {
        "hoge.$": "$.Parameter.Value"
      }
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.hoge",
          "StringMatches": "fuga",
          "Next": "FugaPath"
        }
      ],
      "Default": "NotFugaPath"
    },
    "FugaPath": {
      "Type": "Pass",
      "End": true
    },
    "NotFugaPath": {
      "Type": "Pass",
      "End": true
    }
  }
}

AWS Systems Manager Parameter StoreからGet:Parameterで値を取得し、値の文字列により分岐を行うChoice Stateがあります。

Mock ResponseのConfigです。これによりAWS ServiceからTaskへのレスポンスをMockすることができます。またレスポンスは複数のパターンを記述し、テスト実行時に使用することが可能です。

MockConfigFile.json

{
  "StateMachines": {
    "MyStateMachine": {
      "TestCases": {
        "FugaPathTest": {
          "GetParameter": "GetParameterFugaMockedSuccess"
        },
        "NotFugaPathTest": {
          "GetParameter": "GetParameterNotFugaMockedSuccess"
        }
      }
    }
  },
  "MockedResponses": {
    "GetParameterFugaMockedSuccess": {
      "0": {
        "Return": {
          "Parameter": {
            "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge",
            "DataType": "text",
            "LastModifiedDate": "2022-07-26T05:38:43.052Z",
            "Name": "hoge",
            "Type": "String",
            "Value": "fuga",
            "Version": 4
          }
        }
      }
    },
    "GetParameterNotFugaMockedSuccess": {
      "0": {
        "Return": {
          "Parameter": {
            "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge",
            "DataType": "text",
            "LastModifiedDate": "2022-07-26T05:38:43.052Z",
            "Name": "hoge",
            "Type": "String",
            "Value": "nyao",
            "Version": 4
          }
        }
      }
    }
  }
}

AWS Step Function LocalのDockerイメージamazon/aws-stepfunctions-localをPullします。

docker pull amazon/aws-stepfunctions-local

Pullしたイメージからコンテナを実行します。

docker run -p 8083:8083 \
  --mount type=bind,readonly,source=$(pwd)/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \
  -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" \
  amazon/aws-stepfunctions-local

create-state-machineコマンドを実行してコンテナ上にState Machineを作成します。

stateMachineName=MyStateMachine
aws stepfunctions create-state-machine \
  --endpoint-url http://localhost:8083 \
  --definition file://$(pwd)/MyStateMachine.asl.json \
  --name $stateMachineName \
  --role-arn "arn:aws:iam::123456789012:role/DummyRole"

テスト実行

成功パターン1(FugaPathTest)

start-executionコマンドでStateMachineを実行します。この時State Machineの指定の末尾で、MockConfigFile.jsonで定義したテストケースを#{TestCase}のように指定します。まずはFugaPathTestです。

executionName=$(date +"%s")
aws stepfunctions start-execution \
  --endpoint http://localhost:8083 \
  --name $executionName \
  --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:${stateMachineName}#FugaPathTest

get-execution-historyコマンドで実行履歴を取得します。

$ aws stepfunctions get-execution-history \
  --endpoint http://localhost:8083 \
  --execution-arn arn:aws:states:us-east-1:123456789012:execution:${stateMachineName}:${executionName}
{
    "events": [
        {
            "timestamp": "2022-07-26T15:08:27.554000+09:00",
            "type": "ExecutionStarted",
            "id": 1,
            "previousEventId": 0,
            "executionStartedEventDetails": {
                "input": "{}",
                "inputDetails": {
                    "truncated": false
                },
                "roleArn": "arn:aws:iam::123456789012:role/DummyRole"
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.555000+09:00",
            "type": "TaskStateEntered",
            "id": 2,
            "previousEventId": 0,
            "stateEnteredEventDetails": {
                "name": "GetParameter",
                "input": "{}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.561000+09:00",
            "type": "TaskScheduled",
            "id": 3,
            "previousEventId": 2,
            "taskScheduledEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter",
                "region": "us-east-1",
                "parameters": "{\"Name\":\"hoge\"}"
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.562000+09:00",
            "type": "TaskStarted",
            "id": 4,
            "previousEventId": 3,
            "taskStartedEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter"
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.626000+09:00",
            "type": "TaskSucceeded",
            "id": 5,
            "previousEventId": 4,
            "taskSucceededEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter",
                "output": "{\"hoge\":\"fuga\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.627000+09:00",
            "type": "TaskStateExited",
            "id": 6,
            "previousEventId": 5,
            "stateExitedEventDetails": {
                "name": "GetParameter",
                "output": "{\"hoge\":\"fuga\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.629000+09:00",
            "type": "ChoiceStateEntered",
            "id": 7,
            "previousEventId": 6,
            "stateEnteredEventDetails": {
                "name": "Choice",
                "input": "{\"hoge\":\"fuga\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.635000+09:00",
            "type": "ChoiceStateExited",
            "id": 8,
            "previousEventId": 7,
            "stateExitedEventDetails": {
                "name": "Choice",
                "output": "{\"hoge\":\"fuga\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.635000+09:00",
            "type": "PassStateEntered",
            "id": 9,
            "previousEventId": 8,
            "stateEnteredEventDetails": {
                "name": "FugaPath",
                "input": "{\"hoge\":\"fuga\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.637000+09:00",
            "type": "PassStateExited",
            "id": 10,
            "previousEventId": 9,
            "stateExitedEventDetails": {
                "name": "FugaPath",
                "output": "{\"hoge\":\"fuga\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:08:27.639000+09:00",
            "type": "ExecutionSucceeded",
            "id": 11,
            "previousEventId": 10,
            "executionSucceededEventDetails": {
                "output": "{\"hoge\":\"fuga\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        }
    ]
}

State Machine実行がFugaPathを通り成功したことが確認できました。

成功パターン2(NotFugaPathTest)

次にNotFugaPathTestのパターンです。

executionName=$(date +"%s")
aws stepfunctions start-execution \
  --endpoint http://localhost:8083 \
  --name $executionName \
  --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:${stateMachineName}#NotFugaPathTest

get-execution-historyコマンドで実行履歴を取得します。

$ aws stepfunctions get-execution-history \
  --endpoint http://localhost:8083 \
  --execution-arn arn:aws:states:us-east-1:123456789012:execution:${stateMachineName}:${executionName}
{
    "events": [
        {
            "timestamp": "2022-07-26T15:17:55.153000+09:00",
            "type": "ExecutionStarted",
            "id": 1,
            "previousEventId": 0,
            "executionStartedEventDetails": {
                "input": "{}",
                "inputDetails": {
                    "truncated": false
                },
                "roleArn": "arn:aws:iam::123456789012:role/DummyRole"
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.153000+09:00",
            "type": "TaskStateEntered",
            "id": 2,
            "previousEventId": 0,
            "stateEnteredEventDetails": {
                "name": "GetParameter",
                "input": "{}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.154000+09:00",
            "type": "TaskScheduled",
            "id": 3,
            "previousEventId": 2,
            "taskScheduledEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter",
                "region": "us-east-1",
                "parameters": "{\"Name\":\"hoge\"}"
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.154000+09:00",
            "type": "TaskStarted",
            "id": 4,
            "previousEventId": 3,
            "taskStartedEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter"
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.156000+09:00",
            "type": "TaskSucceeded",
            "id": 5,
            "previousEventId": 4,
            "taskSucceededEventDetails": {
                "resourceType": "aws-sdk",
                "resource": "ssm:getParameter",
                "output": "{\"hoge\":\"nyao\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.156000+09:00",
            "type": "TaskStateExited",
            "id": 6,
            "previousEventId": 5,
            "stateExitedEventDetails": {
                "name": "GetParameter",
                "output": "{\"hoge\":\"nyao\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.156000+09:00",
            "type": "ChoiceStateEntered",
            "id": 7,
            "previousEventId": 6,
            "stateEnteredEventDetails": {
                "name": "Choice",
                "input": "{\"hoge\":\"nyao\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.157000+09:00",
            "type": "ChoiceStateExited",
            "id": 8,
            "previousEventId": 7,
            "stateExitedEventDetails": {
                "name": "Choice",
                "output": "{\"hoge\":\"nyao\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.158000+09:00",
            "type": "PassStateEntered",
            "id": 9,
            "previousEventId": 8,
            "stateEnteredEventDetails": {
                "name": "NotFugaPath",
                "input": "{\"hoge\":\"nyao\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.158000+09:00",
            "type": "PassStateExited",
            "id": 10,
            "previousEventId": 9,
            "stateExitedEventDetails": {
                "name": "NotFugaPath",
                "output": "{\"hoge\":\"nyao\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2022-07-26T15:17:55.158000+09:00",
            "type": "ExecutionSucceeded",
            "id": 11,
            "previousEventId": 10,
            "executionSucceededEventDetails": {
                "output": "{\"hoge\":\"nyao\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        }
    ]
}

State Machine実行がNotFugaPathを通り成功したことが確認できました。

おわりに

AWS Step Function LocalでMock Responseを使った場合のテストを試してみました。

State Machineのテストをしたい場合、今までは実環境を使用する他なかったのですが、その際にAWS Serviceからテストケースで期待したレスポンスを行うようにするのがとても大変で、また限界がありました。AWS Step Function Localを使えば柔軟かつ簡単にテストケースを設定できそうです。

以上