AWS Step Functions Localでモックテストをする

# はじめに

AWS Step Functions Local にモックテストができる機能が1/31に追加されました。
[AWS Step Functions でローカルなワークフローを模擬的にテストすることが可能に](https://aws.amazon.com/jp/about-aws/whats-new/2022/01/aws-step-functions-support-workflows/)

私はこのモック機能でStep Functions Localのことを知ったわけですが、2019年の2月には出ていた機能になります。
[AWS Step Functions ワークフローをローカルで開発してテストする](https://aws.amazon.com/jp/about-aws/whats-new/2019/02/develop-and-test-aws-step-functions-workflows-locally/)

[弊社](https://www.milogos.co.jp/)にはStep Functionsを軸として複数のLambdaを呼ぶシステムがあるため、モックテストを追加していく流れと注意点を紹介します。
実際の導入工程を例にしていますが、値をマスクしたり本記事とズレる箇所は省略したりしています。

![7fc4f0c2-6096-401a-b5e9-dd8621a3c4b8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/1e1f3e7e-b9ec-4cad-77ff-04b72c1a2f91.png)



弊社システムのステートマシーンの定義はこのようなものです。

# Step Functions Localのメリット

- ステートを追加する際に検証がしやすくなります。
- Step Functions LocalからAWS上にある指定したLambdaを呼べるので、動作確認が簡易になります。
- Lambda以外にもStep Functionsが対応しているサービスのレスポンスをモックできます。
- リトライ処理が意図通りかの確認がしやすくなります。

# 前提条件

Step Functions Localを追加するシステムは下記のようなつくりになっています。

- SAM
  - aslファイルはYAMLで記載
  - SAMテンプレートのDefinitionSubstitutionsプロパティでLambdaのARNを指定
- IAM User
  - Lambdaを実行するAWSアカウントのクレデンシャル情報を保持している
- Dockerを使ったローカル開発環境がある

先に記載しますが、Lambdaを呼ぶクレデンシャルに下記の制限が存在します。

- Lambdaを呼ぶ場合、クロスアカウントアクセスはできません。
  - 2022/02/18時点では、ドキュメントへの記載はありませんでした。

上記の条件のもと、いくつか詰まった箇所があったため、まとめていきます。

## ディレクトリ構成

```
.
├── docker-compose.yaml
└── statemachine
    ├── MockConfigFile.json
    ├── aws-stepfunctions-local-credentials.txt
    └── test-sample-statemachine.asl.json
```

## ステートマシーンのデータフロー

以降にASLでの定義ファイルも記載しますが、本記事では本番稼働しているシステムから必要な分のLambdaのみを残したデータフローを例にします。

![5e5fd383-0b13-4b5d-8888-3923d9452569-1920x2336r.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/2bc985a2-c1b6-edb3-4954-addca934e6c1.png)

# Step Functions Localを追加する

[AWS公式ドキュメント](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-test-sm-exec.html#create-mock-config-file)にも実装のステップは記載されていますが、今回は下記の手順で追加、モックテストを実施していきます。

1. Docker ComposeにStep Functions Localを追加
1. Step Functions Localのモック用設定ファイルの追加
1. Step FunctionsのASLファイルの用意
1. ローカルにステートマシーンを作成
1. ハッピーパスの実行
1. リトライパスの実行
1. Hybridパスの実行

## Docker ComposeにStep Functions Localを追加

Step Functions Localの導入には、公式ドキュメントで[Docker版](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-docker.html)と[Java版](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-jar.html)が用意されています。

今回は既にdocker-compose.yamlが開発環境に存在しているので、Docker版を利用しYAMLに追加します。

```yaml
version: "3"
services:
  statemachine:
    image: amazon/aws-stepfunctions-local:latest
    container_name: "statemachine"
    ports:
      - 8083:8083
    environment:
      - SFN_MOCK_CONFIG=/home/StepFunctionsLocal/MockConfigFile.json
    volumes:
      - ./statemachine:/home/StepFunctionsLocal
    env_file:
      - ./statemachine/aws-stepfunctions-local-credentials.txt
    networks:
      lambda-local:
        ipv4_address: 172.18.0.7
networks:
  lambda-local:
    external: false
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.18.0.0/16
          gateway: 172.18.0.1
```

### docker-compose.yamlの各種設定

- image
  - [AWS公式のレポジトリにイメージが存在します](https://hub.docker.com/r/amazon/aws-stepfunctions-local)
- environment
  - 後述のモックテストケースの設定ファイルパスを環境変数に追加します。
- volumes
  - `SFN_MOCK_CONFIG`でセットしたパスに共有できるように設定しています。
  - コンテナのユーザは`stepfunctionslocal`ユーザとなっているため、パスには注意が必要です。
- env_file
  - クレデンシャル情報を記載する、`aws-stepfunctions-local-credentials.txt`のパスを指定して、環境変数に追加します。

## Step Functions Localのモック用設定ファイルの追加

モック機能を使うにあたって、設定ファイルが3つあります。

- aws-stepfunctions-local-credentials.txt
- MockConfigFile.json
- ステートマシーン定義ファイル

### aws-stepfunctions-local-credentials.txt

AWS_ACCESS_KEY_IDなどのクレデンシャル情報や、ローカル開発環境にLocalStackなどを利用してAWSサービスを用意している場合には、LAMBDA_ENDPOINTなどのエンドポイント先をこちらに記載します。
エンドポイントの設定項目名などは、[AWS公式ドキュメント](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-config-options.html#docker-credentials)に記載があります。

```ini
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=**************
AWS_SECRET_ACCESS_KEY=**************
AWS_ACCOUNT_ID=000000000000
WAIT_TIME_SCALE=1
```

ここで`AWS_ACCOUNT_ID`を設定しない場合、内部では`123456789012`がアカウントIDとして設定されます。
以降は、000000000000とマスクしています。

### MockConfigFile.json

モックテストの設定ファイルです。
大きく分けて、`StateMachines`と`MockedResponses`があります。

- StateMachines
  - モック用ステートマシーンの設定です。テストケースに記載された内容が、ローカル環境のステートマシーンでモック実行されます。
- MockedResponses
  - 各ステートに対する、モックのレスポンス設定です。

```json
{
    "StateMachines": {
        "sample-statemachine": {
            "TestCases": {
                "HappyPath": {
                    "Get data": "MockedGetData",
                    "Aggregate messages": "MockedMessages"
                },
                "RetryPath": {
                    "Get data": "MockedGetData",
                    "Aggregate messages": "MockedMessagesRetryStatus",
                },
                "HybridPath": {
                    "Get data": "MockedGetDataFailed"
                }
            }
        }
    },
    "MockedResponses": {
        "MockedGetData": {
            "0": {
                "Return": {
                    "message": "json messages HOGE",
                }
            }
        },
        "MockedMessages": {
            "0": {
                "Return": {
                    "message": "response from mocked state",
                }
            }
        },
        "MockedMessagesRetryStatus": {
            "0-6": {
                "Throw": {
                    "Error": "RetryUntilSentStatusException",
                    "Cause": "not ready."
                }
            },
            "7": {
                "Return": {
                    "StatusCode": 200,
                    "Payload": {
                        "StatusCode": 200,
                        "body": "OK"
                    }
                }
            }
        },
        "MockedGetDataFailed": {
            "0": {
                "Throw": {
                    "Error": "FileNotFoundException",
                    "Cause": "[ERROR] FileNotFoundException Traceback (most recent call last):  File \"/var/task/app.py\", line 72, in lambda_handler    raise FileNotFoundException"
                }
            }
        }
    }
}
```

#### StateMachines部分

```json
"StateMachines": {
  "sample-statemachine": { 
    "TestCases": {
      "HappyPath": {
        "Get data": "MockedGetData",
        "Aggregate messages": "MockedMessages"
      },
      "RetryPath": {
        "Get data": "MockedGetData",
        "Aggregate messages": "MockedMessagesRetryStatus",
      },
      "HybridPath": {
        "Get data": "MockedGetDataFailed"
      }
    }
  }
}
```

StateMachinesの要素である、ステートマシーン名はローカル環境にステートマシーンを作成する、[CreateStateMachine](https://docs.aws.amazon.com/step-functions/latest/apireference/API_CreateStateMachine.html#StepFunctions-CreateStateMachine-request-name)を実行する際の`name`と一致する必要があります。

TestCasesの中には、今回3つのモックテストケースを記載しています。それぞれのパス名をモックテスト実行時に指定することで、そのテストができます。そのため、どのようなモックテストなのか、わかりやすいパス名の方が良さそうです。

- HappyPath
  - 成功する想定のモックテストです。すべてステートをモックテストで構成しています。
- RetryPath
  - エラーレスポンスするモック実行を含んだリトライ処理の確認をします。
  - モックがエラーを返すため、ステートマシーンはステートがエラーと判断し、`Retry`にしたがって複数回リトライします。
- HybridPath
  - エラーレスポンスをモックで返し、エラー時に呼ばれるLambdaをASLファイルの定義に従って実行します。
    - エラーレスポンスを返すモックを実行後、エラー時の後続処理を実行します。
    - MockConfigFile.jsonに書かれていない後続処理は、ASLファイルの`Resource`に従いAWS上のリソースを実行します。

#### MockedResponses部分

```json
"MockedMessagesRetryStatus": {
  "0-6": {
    "Throw": {
      "Error": "RetryUntilSentStatusException",
      "Cause": "not ready."
    }
  },
  "7": {
    "Return": {
      "StatusCode": 200,
      "Payload": {
        "StatusCode": 200,
        "body": "OK"
      }
    }
  }
}
```

`"0-6"`のように記載をすることで、最初の7回は`RetryUntilSentStatusException`のモックレスポンスが返り、8回目で`"7"`で記載しているOKのレスポンスを返すことができます。
これにより、リトライ処理の確認がローカル環境でできます。

動的な並列実行の場合の記載は、[公式ドキュメント](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-mock-cfg-file.html#mock-cfg-struct)に注意書きがあります。

### ステートマシーン定義ファイル

素直には行かなかったポイント1つ目です。先に今回の問題と、その解決方法を記載します。

- 問題
  - create-state-machineコマンドで、既存のYAMLが使えません。
  - create-state-machineコマンドはDefinitionSubstitutionsのようにLambdaのARNを渡せません。
- 解決
  - describe-state-machineコマンドで実際のStep Functionsから定義ファイルを取得、作成します。

#### YAMLが使えない

create-state-machineコマンドでYAMLを指定した場合、InvalidDefinitionエラーになりました。

[create-state-machineのリファレンス](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/stepfunctions/create-state-machine.html)にもある通り、JSONにしか対応していないので、読めば当たり前のことでした。

```bash
aws stepfunctions create-state-machine --endpoint http://localhost:8083 --definition file://sample-statemachine.asl.yaml  --name "sample-statemachine" --role-arn "arn:aws:iam::000000000000:role/service-role/LambdaSQSIntegration"

# An error occurred (InvalidDefinition) when calling the CreateStateMachine operation: Invalid State Machine Definition: ''INVALID_JSON_DESCRIPTION: Unrecognized token 'Comment': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
```

ここでは1行目でCommentを記載しているため、これがJSONになっていないことを指摘されています。

#### ARNを渡せない

SAMテンプレートのDefinitionSubstitutionsで定義していたARNを参照する方法がないため、これまたInvalidDefinitionエラーになりました。
APIリファレンスを確認しましたが、実現する方法がありませんでした。
これまではSAMの恩恵に与っていたということに気づきました。

```bash
aws stepfunctions create-state-machine --endpoint http://localhost:8083 --definition file://sample-statemachine.asl.json --name "sample-statemachine" --role-arn "arn:aws:iam::000000000000:role/service-role/LambdaSQSIntegration"

# An error occurred (InvalidDefinition) when calling the CreateStateMachine operation: Invalid State Machine Definition: ''SCHEMA_VALIDATION_FAILED: Value is not a valid resource ARN at /States/Get data/Resource',
```

## Step FunctionsのASLファイルの用意

YAMLも使えず、ARNも渡せないため、YAMLをJSONに変換し、ARNはsedで書き換えるしかないかと考えましたが、変換後のASLファイルはデプロイされたStep Functionsの定義と同じであることに気がつきました。
そのため、DescribeStateMachineコマンドから定義を取得し、JSONファイルを生成します。

```bash
aws stepfunctions describe-state-machine --state-machine-arn arn:aws:states:ap-northeast-1:000000000000:stateMachine:stg-sample-statemachine | jq '.definition | fromjson' > sample-statemachine.asl.json 
```

今回、利用する定義ファイルは下記のような形です。
実際にシステムで利用している定義ファイルから、モックテストに不要な箇所は省いています。

```json
{
  "Comment": "A API relay state machine",
  "StartAt": "Get data",
  "States": {
    "Get data": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:000000000000:function:stg-data-provider-function",
      "Retry": [
        {
          "ErrorEquals": [
            "RetryServiceException",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 10,
          "MaxAttempts": 3,
          "BackoffRate": 5
        }
      ],
      "Next": "Aggregate messages"
    },
    "Aggregate messages": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:000000000000:function:stg-aggregate-request-function",
      "Retry": [
        {
          "ErrorEquals": [
            "RetryUntilSentStatusException"
          ],
          "IntervalSeconds": 10,
          "MaxAttempts": 7,
          "BackoffRate": 2
        }
      ],
      "Catch": [
        {
          "ErrorEquals": [
            "States.TaskFailed"
          ],
          "ResultPath": "$.Cause",
          "Next": "Failed to execute"
        }
      ],
      "End": true
    },
    "Failed to execute": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:000000000000:function:stg-failed-state-function",
      "Parameters": {
        "error_info.$": "$.Cause"
      },
      "End": true
    }
  }
}
```

## ローカル開発環境にステートマシーンを作成

ここまで用意ができれば、あとはモックテストを実施していくだけです。
まずはコンテナを立ち上げます。

```bash
docker compose up -d
```

ステートマシーンのエンドポイントに対して、CreateStateMachineコマンドを実行し、ステートマシーンを作成します。

```bash
aws stepfunctions create-state-machine --endpoint http://localhost:8083 --definition file://statemachine/test-sample-statemachine.asl.json --name "sample-statemachine" --role-arn "arn:aws:iam::000000000000:role/service-role/LambdaSQSIntegration"

# {
#    "stateMachineArn": "arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine",
#    "creationDate": "2022-02-07T17:32:11.208000+09:00"
# }
```

ARNからわかる通り、アカウントIDはaws-stepfunctions-local-credentials.txtのAWS_ACCOUNT_IDが指定されています。

statemachineコンテナのログ上も、作成されていることが確認できます。

```bash
docker compose logs statemachine

# statemachine    | 2022-02-07 08:32:11.225: [200] CreateStateMachine <= {"sdkResponseMetadata":null,"sdkHttpMetadata":null "stateMachineArn":"arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine","creationDate":1644222731208}
```

## ハッピーパスの実行

StartExecutionコマンドを使います。
`state-machine`オプションのARNの末尾に、`#`とテストパス名を指定するとモック実行ができます。

ステートマシーンの実行名が被らないように、dateコマンドで別名にしています。

```bash
aws stepfunctions start-execution --endpoint http://localhost:8083 --name sample-statemachine-`date +%Y%m%d-%H%M%S` --state-machine arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine#HappyPath

# {
#    "executionArn": "arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220207-193033",
#    "startDate": "2022-02-07T19:30:33.879000+09:00"
# }
```

## リトライパスの実行

RetryPathも同じく、ARNの末尾のみを変えて実行します。

```bash
aws stepfunctions start-execution --endpoint http://localhost:8083 --name sample-statemachine-`date +%Y%m%d-%H%M%S` --state-machine arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine#RetryPath

# {
#    "executionArn": "arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171915",
#    "startDate": "2022-02-08T17:19:15.897000+09:00"
# }
```

コンテナのログから、リトライしている様子が確認できます。

```bash
statemachine    | 2022-02-08 08:19:16.816: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":23,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:19:26.820: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":26,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:19:46.803: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":29,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:20:26.785: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":32,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:21:46.730: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":35,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:24:26.631: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":38,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:29:46.406: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionFailed","PreviousEventId":41,"LambdaFunctionFailedEventDetails":{"Error":"RetryUntilSentStatusException","Cause":"not ready."}}
statemachine    | 2022-02-08 08:40:25.958: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220208-171916 : {"Type":"LambdaFunctionSucceeded","PreviousEventId":44,"LambdaFunctionSucceededEventDetails":{"Output":"{\"StatusCode\":200,\"body\":\"OK\"}"}}
```

ASLファイルではリトライ処理を下記のように設定していました。

```json
"IntervalSeconds": 10,
"MaxAttempts": 7,
"BackoffRate": 2
```

リトライ時間は`IntervalSeconds * BackoffRate^(n - 1) (n >1の時)`になるため、10、20、40、80、160、320秒ごとにリトライ処理でモック実行が走り、その都度、エラーレスポンスを返しています。
そして、7回目のリトライから640秒後、最初の実行から8回目の実行であるリトライ処理時に、OKの成功レスポンスが返ってきていることが確認できます。

## Hybridパスの実行

こちらも同じく、ARNの末尾のみを変えて実行します。

```bash
aws stepfunctions start-execution --endpoint http://localhost:8083 --name sample-statemachine-`date +%Y%m%d-%H%M%S` --state-machine arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine#HybridPath

# {
#     "executionArn": "arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220221-172535",
#     "startDate": "2022-02-21T17:25:36.607000+09:00"
# }
```

コンテナのログにもステートの遷移が確認できますが、Lambdaを呼んでいるのかは少しわかりづらいです。

```bash
statemachine    | 2022-02-21 08:28:57.590: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220221-172856 : {"Type":"LambdaFunctionFailed","PreviousEventId":4,"LambdaFunctionFailedEventDetails":{"Error":"FileNotFoundException","Cause":"[ERROR] FileNotFoundException Traceback (most recent call last):??File \"/var/task/aws_lambda_powertools/tracing/tracer.py\", line 315, in decorate                 ????response = lambda_handler(event, context, **kwargs)??File \"/var/task/aws_lambda_powertools/metrics/metrics.py\", line 184, in decorate????response = lambda_handler(event, context)??File \"/var/task/app.py\", line 72, in lambda_handler????raise FileNotFoundException"}}
statemachine    | 2022-02-21 08:36:49.853: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220221-173648 : {"Type":"LambdaFunctionSucceeded","PreviousEventId":9,"LambdaFunctionSucceededEventDetails":{"Output":"null"}}
statemachine    | 2022-02-21 08:36:49.853: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220221-173648 : {"Type":"TaskStateExited","PreviousEventId":10,"StateExitedEventDetails":{"Name":"Failed to execute","Output":"null"}}
statemachine    | 2022-02-21 08:36:49.854: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220221-173648 : {"Type":"ExecutionSucceeded","PreviousEventId":11,"ExecutionSucceededEventDetails":{"Output":"null"}}
```

- 1つ目のログ
  - statemachine localで意図的にエラーとなるモック実行が走ります。ここでは、FileNotFoundExceptionを起こします。
- 3つ目のログ
  - ASLファイルの記載に従ってLambdaがFailedした場合、`Failed to execute`のステートに遷移します。
- 4つ目のログ
  - MockConfigFile.jsonに`Failed to execute`のステートが記載されていない場合、`Resource`のARNが呼ばれます。

実際に呼ばれたLambdaのCloudWatch Logsの様子です。
モック実行から渡したエラーメッセージが、ログに来ていることがわかります。

![ae65e149-5a6f-4efa-984e-2950a5428fd4-1920x604r.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224198/50713aaa-1bb6-e2d4-db66-5346db93c126.png)

### クロスアカウントでLambdaは呼べない

上手く行かず、方針を変えたポイントです。
クロスアカウントでLambdaを呼び出そうとすると、エラーになります。

```bash
aws stepfunctions start-execution --endpoint http://localhost:8083 --name sample-statemachine-`date +%Y%m%d-%H%M%S` --state-machine arn:aws:states:ap-northeast-1:000000000000:stateMachine:sample-statemachine#HybridPath
```

`000000000000`のIAMユーザから`000000000001`のLambdaにアクセスしようとしていました。

```bash
statemachine    | 2022-02-14 07:45:16.829: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220214-164516 : {"Type":"LambdaFunctionFailed","PreviousEventId":4,"LambdaFunctionFailedEventDetails":{"Error":"FileNotFoundException","Cause":"[ERROR] FileNotFoundException Traceback (most recent call last):??File \"/var/task/aws_lambda_powertools/tracing/tracer.py\", line 315, in decorate                 ????response = lambda_handler(event, context, **kwargs)??File \"/var/task/aws_lambda_powertools/metrics/metrics.py\", line 184, in decorate????response = lambda_handler(event, context)??File \"/var/task/app.py\", line 72, in lambda_handler????raise FileNotFoundException"}}
statemachine    | 2022-02-14 07:45:17.479: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220214-164516 : {"Type":"LambdaFunctionFailed","PreviousEventId":9,"LambdaFunctionFailedEventDetails":{"Error":"Lambda.ResourceNotFoundException","Cause":"Function not found: arn:aws:lambda:ap-northeast-1:000000000000:function:stg-sample-failed-state-function (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: 12a08bcc-acce-45d3-986f-a2049ca6eb30; Proxy: null)"}}
statemachine    | 2022-02-14 07:45:17.482: arn:aws:states:ap-northeast-1:000000000000:execution:sample-statemachine:sample-statemachine-20220214-164516 : {"Type":"ExecutionFailed","PreviousEventId":10,"ExecutionFailedEventDetails":{"Error":"Lambda.ResourceNotFoundException","Cause":"Function not found: arn:aws:lambda:ap-northeast-1:000000000000:function:stg-sample-failed-state-function (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: 12a08bcc-acce-45d3-986f-a2049ca6eb30; Proxy: null)"}}
```

残念ながら、AWS_ACCOUNT_IDで設定していたアカウントIDのリソースにしかアクセスできませんでした。

また、aws-stepfunctions-local-credentials.txtのAWS_ACCOUNT_IDをセットしなかった場合、エラーメッセージのARNはAWS_ACCESS_KEY_IDに設定した、IAMユーザのアカウントIDに変わりました（驚）。

```bash
"Cause":"Function not found: arn:aws:lambda:ap-northeast-1:000000000000:function:stg-sample-failed-state-function ... "
```

### どうしてもクロスアカウントでLambdaを呼びたい

弊社のシステムにモックテストを追加するにあたっては、実際のASL定義から離れるため実施していませんが、下記のようにクロスアカウント先のLambdaを呼ぶ手もあります。

- SNSを利用する
  - Lambdaを実行するステートの代わりにSNSを設定し、StepFunctions localからSNSを呼ぶようにASLファイルを指定します。
  - SNSからクロスアカウント先のLambdaに通知をします。
- LambdaからLambdaをinvokeする
  - 呼び元のアカウントに、[Invoke](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/API_Invoke.html)するLambdaを作成します。
  - Lambdaを実行するステートの代わりに、invokeするLambdaを設定します。

# おわりに

既存の実際に本番稼働しているシステムに対して、Step Functions Localでのモックテストを追加してみました。
Step FunctionsがAWSの多くのサービスに対応したことで、今後は様々なシーンで利用機会が増えそうです。
その際に今回のモック機能を導入することで開発が楽になり、この記事がその導入の一助になれば嬉しいです。