この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部 IoT事業部の若槻です。
前回の下記エントリでは、AWS Step Functions LocalによるMockテストをJestで実行してみました。
今回は、同じくAWS Step Functions LocalとJestによるState MachineのMockテストをGitHub Actionsで実行してみました。
やってみた
実装のファイル構成は次のようになります。
$ tree
.
├── .github
│ └── workflows
│ └── ci.yml
├── jest.config.js
├── package-lock.json
├── package.json
├── test
│ └── mock
│ └── stepfunctions
│ ├── MyStateMachine
│ │ ├── MockConfigFile.json
│ │ ├── MyStateMachine.asl.json
│ │ └── MyStateMachine.test.ts
│ └── docker-compose.yml
└── tsconfig.json
導入しているパッケージです。
$ npm ls --depth=0
project@0.1.0 /path/to/project
├── @types/jest@28.1.6
├── @types/node@18.6.2
├── jest@28.1.3
├── ts-jest@28.0.7
├── ts-node@10.9.1
└── typescript@4.7.4
Test files
test/mock/stepfunctions/MyStateMachine
配下ではテスト内容に関するファイルを管理しています。
MockConfigFile.json
は、テスト対象のState Machine(後述)に対するMock ResponseのConfigです。これによりAWS ServiceからTaskへのレスポンスをMockすることができます。またレスポンスは複数のパターンを記述し、テスト実行時に使用することが可能です。
test/mock/stepfunctions/MyStateMachine/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
}
}
}
}
}
}
MyStateMachine.asl.json
は、テスト対象となるState MachineのASL(Amazon States Language)定義のファイルです。
test/mock/stepfunctions/MyStateMachine/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
}
}
}
MyStateMachine.test.ts
は、Jestのテストコードです。上述のASLファイルを使用してコンテナ上にState Machieを作成して実行し、実行が成功していることおよびMock Responseで定義したテストケース毎に想定したパスを経由していることを確認しています。
test/mock/stepfunctions/MyStateMachine/MyStateMachine.test.ts
import {
SFNClient,
GetExecutionHistoryCommand,
StartExecutionCommand,
CreateStateMachineCommand,
DescribeExecutionCommand,
ExecutionStatus,
HistoryEvent,
DeleteStateMachineCommand,
} from '@aws-sdk/client-sfn';
import TEST_TARGET_ASL = require('./MyStateMachine.asl.json');
const DUMMY_AWS_REGION = 'us-east-1';
const DUMMY_AWS_ACCOUNT = '123456789012';
const STEP_FUNCTIONS_LOCAL_ENDPOINT = 'http://localhost:8083';
const DUMMY_EXECUTION_ROLE_ARN = `arn:aws:iam::${DUMMY_AWS_ACCOUNT}:role/DummyRole`;
const STATE_MACHINE_NAME = 'MyStateMachine';
const DUMMY_STATE_MACHINE_ARN = `arn:aws:states:${DUMMY_AWS_REGION}:${DUMMY_AWS_ACCOUNT}:stateMachine:${STATE_MACHINE_NAME}`;
const sfnClient = new SFNClient({
region: DUMMY_AWS_REGION,
credentials: { accessKeyId: 'dummy', secretAccessKey: 'dummy' },
endpoint: STEP_FUNCTIONS_LOCAL_ENDPOINT, //AWS Step Functions Localのエンドポイントを指定
});
//State Machineの作成
beforeAll(async () => {
await sfnClient.send(
new CreateStateMachineCommand({
name: STATE_MACHINE_NAME,
roleArn: DUMMY_EXECUTION_ROLE_ARN,
definition: JSON.stringify(TEST_TARGET_ASL),
}),
);
});
//State Machineの削除
afterAll(async () => {
await sfnClient.send(
new DeleteStateMachineCommand({
stateMachineArn: DUMMY_STATE_MACHINE_ARN,
}),
);
});
//State Machine実行の開始および履歴取得
const startExecutionAndGetExecutionHistory = async (
testCase: string,
): Promise<HistoryEvent[]> => {
const executionResult = await sfnClient.send(
new StartExecutionCommand({
name: testCase,
stateMachineArn: `${DUMMY_STATE_MACHINE_ARN}#${testCase}`,
}),
);
const executionArn = executionResult.executionArn;
let executionStatus = 'RUNNING';
while (executionStatus === 'RUNNING') {
await new Promise((r) => setTimeout(r, 1000));
const res = await sfnClient.send(
new DescribeExecutionCommand({
executionArn: executionArn,
}),
);
if (res.status !== 'RUNNING')
executionStatus = res.status as ExecutionStatus;
}
const executionHistory = await sfnClient.send(
new GetExecutionHistoryCommand({
executionArn: executionArn,
includeExecutionData: true,
}),
);
return executionHistory.events as HistoryEvent[];
};
describe('MyStateMachine', () => {
test('FugaPathTest', async () => {
const executionHistory = await startExecutionAndGetExecutionHistory(
'FugaPathTest',
);
//State Machine実行の分岐がFugaPathを経由していることを確認
expect(executionHistory[8].stateEnteredEventDetails?.name).toBe('FugaPath');
//State Machine実行が成功していることを確認
expect(executionHistory[10].type).toBe('ExecutionSucceeded');
});
test('NotFugaPathTest', async () => {
const executionHistory = await startExecutionAndGetExecutionHistory(
'NotFugaPathTest',
);
//State Machine実行の分岐がNotFugaPathを経由していることを確認
expect(executionHistory[8].stateEnteredEventDetails?.name).toBe(
'NotFugaPath',
);
//State Machine実行が成功していることを確認
expect(executionHistory[10].type).toBe('ExecutionSucceeded');
});
});
Docker Compose file
AWS Step Functions LocalのコンテナImageを実行するためのDocker Composeの構成ファイルです。実行時にMockConfigFile.json
をマウントします。
test/mock/stepfunctions/docker-compose.yml
version: '3'
services:
sfn_local:
image: amazon/aws-stepfunctions-local
volumes:
- ./MyStateMachine/MockConfigFile.json:/home/StepFunctionsLocal/MockConfigFile.json
environment:
SFN_MOCK_CONFIG: /home/StepFunctionsLocal/MockConfigFile.json
ports:
- 8083:8083
Workflow file
GitHub ActionsのWorkflowのファイルです。docker compose
コマンドでAWS Step Functions Local環境を立ち上げ、Jestコマンドでテストを実行しています。
.github/workflows/ci.yml
name: CI
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache CDK Dependency
uses: actions/cache@v3
id: cache_cdk_dependency_id
env:
cache-name: cache-cdk-dependency
with:
path: node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-
- name: Clean install Dependency
if: ${{ steps.cache_cdk_dependency_id.outputs.cache-hit != 'true' }}
run: npm ci
- name: Run container image
working-directory: test/mock/stepfunctions
run: docker compose up -d
- name: Run Step Functions mock test
run: npx jest test/mock/stepfunctions
動作確認
Workflowを実行すると、テストが実行されSuccessしました。
Run npx jest test/mock/stepfunctions
npx jest test/mock/stepfunctions
shell: /usr/bin/bash -e {0}
PASS test/mock/stepfunctions/MyStateMachine/MyStateMachine.test.ts (10.315 s)
MyStateMachine
✓ FugaPathTest (1180 ms)
✓ NotFugaPathTest (1046 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.426 s
Ran all test suites matching /test\/mock\/stepfunctions/i.
おわりに
GitHub ActionsでAWS Step Functions LocalとJestによるステートマシンのMockテストを実行してみました。
これでコードのPush時などにStep FunctionsのMockテストをCI Workflowの一環として実行できるようになりました。次回はさらにAWS CDKで開発をしている場合にASLを自動でテストに使用できるようにしたいです。
参考
以上