LocalStackを使ったテストをCircleCIのCI/CDで実行してみた
CX事業本部 Delivery 部のアベシです。
先日投稿したこちらのブログでLocalStackを使ったテストを紹介しました。
今回はこちらのテストをCircleCIのCI/CDの過程でできるようにしてみましたので、その際の設定や手順をご紹介したいと思います。
大まかな流れ
以下の流れでやっていきます。
- プロジェクトにCircleCIの設定ファイル(.circleci/config.yml)を作成する
- リポジトリをCircleCIの管理下に設定する
- ダミーのAWSクレデンシャルをCircleCIの環境変数に登録する
- 変更をGitHubにプッシュしてCI/CDの結果がSucceessとなればOK
コード
ソースコード
テスト対象のLambda関数のソースコードの処理は、S3にGetObjectを実行し、バケットに保存されているJSONファイルを取得して返却する、です。
今回使用するテスト対象のLambda関数及びテストコードは上に載せたブログで紹介したものと同じですので説明は省きます。折りたたんだものを下に掲載します。
こちら開くとコードが確認できます。
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; interface Event { key: string; } interface Response { prefecture_code: string; prefecture: string; } export const handler = async (event: Event): Promise<Response> => { const awsConfig = { endpoint: process.env.S3_ENDPOINT_LOCALSTACK || '', region: 'ap-northeast-1', }; const s3Client = new S3Client(awsConfig); try { const response = await s3Client.send( new GetObjectCommand({ Bucket: "sample-get-object-to-s3", Key: event.key, }), ); const stringResponse = await response.Body?.transformToString(); const objectResponse = JSON.parse(stringResponse as string); return objectResponse; } catch (error) { console.error(error); throw error; } };
テストコード
テストコードも前回のブログで紹介したものと同じですので説明は省きます。ソースコードと同じように折りたたんだものを下に掲載します。
こちら開くとコードが確認できます。
process.env.S3_ENDPOINT_LOCALSTACK = 'https://s3.localhost.localstack.cloud:4566'; import { S3Client, CreateBucketCommand, PutObjectCommand, DeleteBucketCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import * as fs from 'fs'; import * as path from 'path'; import { handler } from '../../../src/lambda/handlers/get-object-to-s3'; const s3EndpointLocalStack = 'https://s3.localhost.localstack.cloud:4566'; const awsConfig = { endpoint: s3EndpointLocalStack, region: 'ap-northeast-1', }; const s3Client = new S3Client(awsConfig); describe('正常系', () => { const event = { key: 'sample/value1', }; beforeAll(async () => { await s3Client.send( new CreateBucketCommand({ Bucket: 'sample-get-object-to-s3' }), ); await s3Client.send( new PutObjectCommand({ Body: fs.readFileSync(path.join(__dirname, 'test-data.json')), Bucket: 'sample-get-object-to-s3', Key: 'sample/value1', }), ); }); afterAll(async () => { await s3Client.send( new DeleteObjectCommand({ Bucket: 'sample-get-object-to-s3', Key: 'sample/value1', }), ); await s3Client.send( new DeleteBucketCommand({ Bucket: 'sample-get-object-to-s3' }), ); }); test('テストデータのオブジェクトを返却する事を確認', async () => { const response = await handler(event); expect(response).toHaveProperty('prefecture_code'); expect(response.prefecture_code).toBe('001'); expect(response).toHaveProperty('prefecture'); expect(response.prefecture).toBe('北海道'); }); });
.circleci/config.ymlファイルの作成
config.ymlファイルの内容は以下のようになります。
CircleCIでのCI/CDの内容や環境設定などはこのファイルに記載します。
version: 2.1 jobs: test_and_build: docker: - image: cimg/node:18.16.0 - image: localstack/localstack:2.0.1 steps: - checkout - restore_cache: key: v1-dependencies-{{ .Branch }}-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }} - run: name: Install npm dependencies command: | set -x [[ -d node_modules ]] || npm ci - save_cache: paths: - node_modules key: v1-dependencies-{{ .Branch }}-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }} - run: name: Test unit command: | set -x npm run unit-test workflows: test_workflows: jobs: - test_and_build: filters: branches: only: - /.*/
config.ymlファイルの解説
config.ymlのバージョンは今使える最新の2.1
を使用しています。
jobs:
jobs:
にビルドとテストの内容を記載しています。
docker:
jobs:
配下のdocker:
にはテストとビルドに使用する実行環境イメージを設定しています。
- Node.JS実行用のイメージ
cimg/node:18.16.0
を使用しています。
cimg/node
はCircleCI
が提供する公式のDockerイメージでNode.jsを実行するためのイメージです。 今回LambdaのランタイムがNode.js18なので、イメージもNode.js18が使える18.16.0
を指定しています。
- LocalStack実行用のイメージ
- localstack/localstack:2.0.1を使用しています。
localstack/localstack
はlocalstack
が提供する公式のDockerイメージです。今回なるべく新しいものを使用しています。
- localstack/localstack:2.0.1を使用しています。
steps:
steps:
には依存関係のインストールやテストとビルドのコマンドを記載します。
checkout:
checkoutではGitHubからコードをgit cloneで取得し、対象のブランチにスイッチしています。
restore_cache:
restore_cacheでは前回のCI/CDでキャッシュしたnode_moduleを復元する工程になります。
保存されているkey:
と比べて値が変化していれば復元します。
key:
にはpackage.json
とpackage-lock.json
のハッシュ値を指定しています。
インストールしたパッケージに変更が有るとこのハッシュ値が変化します。変化が有る場合はnode_module
も変化しているのでキャッシュしたnode_moduleは復元せずに次のステップでパッケージをnode_moduleにインストールします。
restoreの工程を入れることで、新たなパッケージのインストールが無い場合はパッケージのインストールがスキップされてCI/CDの実行にかかる時間を短縮できます。
Install npm dependencies
次のrun:
ではnpm ci
コマンドを実行しています。npm ci
はpackage-lock.json
を元にパッケージをnode_moduleにインストールします。
もしnode_moduleを復元している場合は、[[ -d node_modules ]] || npm ci
の評価によってnpm ci
は実行されません。[[ -d node_modules ]]はnode_modulesディレクトリが存在する場合はtrueを返し、存在しない場合はfalseを返して ||
の後のnpm ci
が実行されます。
save_cache:
save_cache:
はnode_module
をキャッシュする工程です。
もしkey:のハッシュ値に変化がなければこの工程はスキップされます。
workflows:
workflows:
には実行するjobを記載します。先程説明したtest_and_buildを指定します
filters:
filters:
にはjobを実行する対象のブランチを指定します。指定したブランチに対してプッシュやマージがあった場合にjobが実行されます。
今回はすべてのブランチを対象にする、という意味で/.*/
を指定しています。
リポジトリをCircleCIの管理下に設定する
CircleCIのダッシュボードからGitHubのリポジトリをFollowします。
まずは左ページのProjects
を開きます。
CircleCIの管理下に置きたいリポジトリのSet Up Project
をクリックします。
出てきたモーダルでconfig.ymlを選択します。今回は既にconfig.ymlを作成してプッシュしている状況を想定しますので、
一番上のuse the .circleci/config.yml
を選択します。
最後にSet Up Project
をクリックして完了です。
ダミーのAWSクレデンシャルをCircleCIの環境変数に登録する
LocalStackではダミーの値で良いのですがAWSのクレデンシャルの情報が必要なので、CircleCIの環境変数に登録します。
左ペインのprojectをクリックして、対象のリポジトリの3点メニューを開きます。
メニューのproject settingsをクリックします。
リポジトリの設定画面に遷移するのでEnvironment Variablesをクリックします。
切り替わった画面でAdd Environment Variable
から変数を登録します。
以下の2つを登録します。
- AWS_ACCESS_KEY_ID : dummy
- AWS_SECRET_ACCESS_KEY : dummy
これらのクレデンシャルを登録しないでCI/CDを実行すると以下のようなエラーが発生します。
● Test suite failed to run CredentialsProviderError: Could not load credentials from any providers
動作確認
適当に変更加えてプッシュします。
以下のようにSuccess
というステータスが表示されていばjobがすべて成功となります。
補足
Container localstack/localstack:2.0.1
のjobが灰色丸に横棒のステータスとなり、エラーでも成功でもない結果となって、ログにBuild was canceled
と出力されています。こちらの記事のCircleCIからの解答によると、テストjobの完了までこのjobは動き続け、テスト完了と共にBuild was canceled
と出力されるとの事で、特に問題のある動きではないようです。