lambda + typescript + s3のリファクタリングをしてテストケースを書いたメモ

## 概要
[前回はS3をローカルで立ち上げた。](https://qiita.com/hibohiboo/items/e81573744466343aec2c)
今回は、[ローカル環境でLambda+S3のテストをする](https://qiita.com/billthelizard/items/22d2457f3d6386d21796)を参考に、リファクタリングをしてテストを行う。

[ソースコード](https://github.com/hibohiboo/develop/tree/481815420c60a0933e51483ded6ed53d1da64c03/tutorial/lesson/aws/typescript/projects/cdk_sample)

## 構造

```
src
  - lambda
    - handlers
      - s3-sample.ts
    - service
      - s3-sample.ts
test
  - fixture
    - s3
      - message.txt
  - lambda
    - service
      - s3-sample.test.ts
```

## ソース

* 処理をgetS3Objectに移し、インスタンスを作って渡すようにする。

```ts:src/lambda/hendler/s3-sample.ts
import { S3 } from 'aws-sdk';
import { Handler } from 'aws-lambda';
import { getS3Object } from '../service/s3-sample';

const s3 = new S3();
const { S3_BUCKET } = process.env;
if (!S3_BUCKET) throw Error('env S3_BUCKET is empy')

export const lambdaHandler: Handler = async (event, context, callback) => {
  try {
    const message = getS3Object({ s3, key: event.Key, bucket: S3_BUCKET, callback });
    console.log(message)
  } catch (err) {
    console.log(err);
    return err;
  }
};
```

* getS3Objectでは、使用するインスタンスは外部から受け取る。DI。

```ts:src/lambda/service/s3-sample.ts
import type { S3 } from 'aws-sdk';
import { Callback } from 'aws-lambda';
interface Payload {
  s3: S3
  key: string
  bucket: string
  callback: Callback
}
export const getS3Object: (o: Payload) => Promise<string> = async ({ s3, bucket, key, callback }) => {
  const params = {
    Bucket: bucket,
    Key: key
  };
  const ret = await s3.getObject(params).promise();
  if (!ret.Body) throw Error(`s3 object is empty. Bucket: ${bucket}, Key: ${key}`)
  const message = ret.Body.toString();
  console.log(message);
  callback(null, `access ${key}`)
  return message;
}
```

* テストではs3オブジェクトを作成して渡してやる。

```ts:test/lambda/service/s3-sample.test.ts
import * as fs from 'fs';
import * as path from 'path';
import { S3 } from 'aws-sdk';
import { getS3Object } from '@/lambda/service/s3-sample';
// localstack
const config = {
  endpoint: 'http://localhost:4566',
  s3ForcePathStyle: true,
};

const s3 = new S3(config);

/*
 * test suits
 */
describe('core/getS3Object test suit #1', () => {
  // shared variables over the test suit
  let bucket: string;
  let key: string;
  const callback = () => { }

  /*
   * starup
  */
  beforeEach(async () => {
    // generate test resource sequence code from timestamp
    const now = new Date().getTime();
    bucket = `test-bucket-${now}`;
    key = `message_${now}.txt`;
    // create bucket & bucket key
    await s3.createBucket({ Bucket: bucket }).promise();
    await s3.putObject({
      Bucket: bucket,
      Key: key,
      ContentType: 'text/plain',
      Body: fs.readFileSync(path.join(__dirname, '..', '..', 'fixture', 's3', 'message.txt')),
    }).promise();
  });

  /*
   * test cases
  */
  it('successfully get an object from S3 bucket', async () => {
    const message = await getS3Object({ s3, bucket, key, callback });
    expect(message).toBe('Hi, there.\n');
  });

  it('throw error if access to non-existent key', async () => {
    try {
      await getS3Object({ s3, bucket, key: 'non-existent', callback })
      fail()
    } catch (error) {
      expect(error.name).toBe('NoSuchKey');
      expect(error.message).toBe('The specified key does not exist.');
    }
  });

  it('throw error if access to non-existent bucket', async () => {
    try {
      await getS3Object({ s3, bucket: 'non-existent', key: 'non-existent', callback })
      fail()
    } catch (error) {
      expect(error.name).toBe('NoSuchBucket');
      expect(error.message).toBe('The specified bucket does not exist');
    }

  });

  /*
   * tear-down
   */
  afterEach(async () => {
    // delete bucket key & bucket
    await s3.deleteObject({
      Bucket: bucket,
      Key: key,
    }).promise();
    await s3.deleteBucket({ Bucket: bucket }).promise();
  });
});
```




## 参考
[Testable - lambda : 和田卓人](https://speakerdeck.com/twada/testable-lambda-working-effectively-with-legacy-lambda)
[ローカル環境でLambda+S3のテストをする](https://qiita.com/billthelizard/items/22d2457f3d6386d21796)
[github aws-sam-localstack-example](https://github.com/bilzard/aws-sam-localstack-example)
[DI・DIコンテナ、ちゃんと理解出来てる・・？](https://qiita.com/ritukiii/items/de30b2d944109521298f)