Edited at

AWS Lambda を TypeScript で開発する。テスト出来る環境のセットアップまで。

More than 3 years have passed since last update.

Lambda 最高ですね。

型が欲しいのでとりあえず TypeScript で開発出来るようにしてみます。

S3 Put Event を使った Lambda func の完成系はこんな感じ。

/// <reference path="lambda.d.ts" />

exports.handler = function(event: S3PutEvent, context: Context) {
console.log(event.Records[0].eventTime);
console.log(event.Records[0].s3.object.key);

context.done(null, "done!");
}


d.ts を書く

ザックリ宣言します。


lambda.d.ts

declare var exports: Exports;

declare class Exports {
public handler: Function;
}

interface Context {
done(err: any, value: string): void;
invokeid: string;
}


S3 Put Event のほうはチョット多い。

// S3 Put Event

declare class S3PutEvent {
Records: Array<S3PutRecord>
}

declare class S3PutRecord {
eventVersion: string;
eventSource: string;
awsRegion: string;
eventTime: string;
eventName: string;
userIdentity: S3UserIdentity;
requestParameters: S3RequestParameters;
responseElements: S3ResponseElements;
s3: S3;
}

declare class S3UserIdentity {
principalId: string;
}

declare class S3RequestParameters {
sourceIPAddress: string;
}

declare class S3ResponseElements {
"x-amz-request-id": string;
"x-amz-id-2": string;
}

declare class S3 {
s3SchemaVersion: string;
configurationId: string;
bucket: S3Bucket;
object: S3Object;
}

declare class S3Bucket {
name: string;
ownerIdentity: S3UserIdentity;
arn: string;
}

declare class S3Object {
key: string;
size: number;
eTag: string;
}

こんな感じにやることで、補完も効いてコンパイル時にちゃんと怒られる型ライフを過ごすことが出来ます。


テスト出来るようにする

handler に渡すべく、fake の context を実装します。

/// <reference path="../../lambda.d.ts" />

declare var module: any;

class LambdaContext implements Context {
invokeid:string;

constructor (invokeid: string) {
this.invokeid = invokeid;
}

done(err:any, value:string):void {
return;
}
}

(module).exports = new LambdaContext("someid");

done が呼ばれても何もしないようにします。

これで、下記のように Lambda Function が呼べるようになりました。

var lambda = require("../app");

var context = require("./driver/context");
var event = require("./driver/s3_put_event");

lambda.handler(event, context);


Sinon で context#done が呼ばれることをテストする

ここから js です。mocha + chai を使っています。

var chai = require("chai");

var expect = chai.expect;
var sinon = require("sinon");
var context = require("./driver/context");
var event = require("./driver/s3_put_event");

describe('lambda', function() {
it("calls context#done", function() {
var spy = sinon.spy(context, "done");

var lambda = require("../app");
lambda.handler(event, context);

expect(spy.called).to.eq(true);
});

afterEach(function() {
context.done.restore();
});
});

この書き方だと同期的な処理しかテスト出来ません。


非同期テストする

非同期テストをするには itdone: Function を引数にとり、完了時に done() します。

sinon#stub ですげ替えてあげましょう。

describe('lambda', function() {

it("calls context#done async", function(done) {
sinon.stub(context, "done", function(err, value) {
expect(err).to.eq(null);

done();
});

var lambda = require("../app");
lambda.handler(event, context);
});

afterEach(function() {
context.done.restore();
});
});


DI する

テストのために、依存ライブラリを内部で直接呼ぶのではなく、依存ライブラリを受け取って使う形にする。所謂 DI.

mock とか stub とかして渡す余地は作っておきましょう。

高尚なことをしなくとも第 3 引数以降から依存ライブラリを受け取るだけです。通常の AWS からの呼び出しであれば常に第 3 引数以降は省略されるので、undefined であれば普通に require します。

var _someLibrary = require("someLibrary");

exports.handler = function(event: S3PutEvent, context: Context, someLibrary?: SomeLibrary) {
if (!someLirary) {
someLibrary = _someLibrary;
}

console.log(event[0].eventTime);
console.log(event[0].s3.object.key);

// someLibrary を使って何かする

context.done(null, "done!");
}

このように DI 出来るようにしてあげることで、次のように someLibrary を mock したり stub したり出来ます。

var stub = sinon.stub(someLibrary, "someFunc");

stub.returns(0);

lambda.handler(event, context, someLibrary);


レポジトリ

https://github.com/kaiinui/lambda_kit にあげてます


所見


  • WebStorm + TypeScript は補完バリバリ効くし、魔法の Shift+Enter があるので楽。

  • AWS Lambda は TypeScript の型定義の手間がそんなに無いので、TS に向いている(私見)

  • Event は絶対に型定義して使った方が良い。

  • Lambda はテストしやすいし、DI もやりやすいのでテストに向いている。1


References