motoのmock_dynamodbを使ってみました

# やりたいこと
LambdaでDynamoDBからデータを取得するプログラムの単体テストをやるため、DynamoDB関連の処理をモック化したい

# 前提
・Pythonインストール済み
・pipenvインストール済み
（今回は便宜上pipenv内でtoxでテストをやるのですが、ローカルで諸々ライブラリを入れてテストをやっても大丈夫です）

# ディレクトリ構成
```ruby:
root 　　   
  ┣━ functions
      ┗━ api
          ┗━ app.py
  ┣━ tests
      ┣━ conftest.py
      ┗━ test_app.py
  ┣━ Pipfile
  ┗━ tox.ini
```

# ソースの中身
ごく簡単の処理ですが、あるDynamoDBテーブルからget_itemをやりたい
```ruby:app.py
import boto3
import os

# DynamoDB関連情報はLambdaの環境変数から取る
DYNAMO_TABLE_NAME = os.environ.get('DYNAMO_TABLE_NAME')
DYNAMO_REGION = os.environ.get('DYNAMO_REGION', default="ap-southeast-1")
dynamodb = boto3.resource('dynamodb', region_name=DYNAMO_REGION)

def lambda_handler(event):
    # keyの値はリクエストbody値から取る
    id = event['body']['id']
    name = event['body']['name']
    
    key = {
            'id': id,
            'name': name,
        }
    data = dynamodb.Table(DYNAMO_TABLE_NAME).get_item(Key=key)

    return data
```
テスト時は本物のDynamoDBにアクセスしないように、テストの前準備として、motoでmock化したDynamoDBテーブルを作りDummyのデータをputする関数を用意しておきます。

これは、単体テストを実行するとき、先にmock_tableが実行され、create_authority_table関数を返すような処理です。

```ruby:conftest.py
import pytest
import boto3


DYNAMO_REGION = 'ap-southeast-1'
DYNAMO_TABLE_NAME = 'auth_table'


@pytest.fixture(scope='package')
def mock_table():
    def create_authority_table():
        ddb = boto3.resource('dynamodb', region_name=DYNAMO_REGION)
        # Dummyテーブルを作る
        table = ddb.create_table(
            TableName=DYNAMO_TABLE_NAME ,
            KeySchema=[
                {
                    'AttributeName': 'id',
                    'KeyType': 'HASH'
                },
                {
                    'AttributeName': 'name',
                    'KeyType': 'RANGE'
                },
            ],
            AttributeDefinitions=[
                {
                    'AttributeName': 'id',
                    'AttributeType': 'S'
                },
                {
                    'AttributeName': 'name',
                    'AttributeType': 'S'
                },
            ],
            ProvisionedThroughput={
                'ReadCapacityUnits': 1,
                'WriteCapacityUnits': 1
            }
        )

        # ちょっと待ち時間を置く
        table.wait_until_exists()

        # Dummyデータ投入
        table.put_item(
                Item={
                    'id': '0001',
                    'name': 'satoh',
                    'company': 'A',
                },
        )

        return ddb.Table(DYNAMO_TABLE_NAME)
    return create_authority_table
```
@mock_dynamodb()を書くと、本物のDynamoDBを見ずに、mock_tableで作られたテーブルを見に行くことになります。

```ruby:test_app.py
import importlib
import pytest
from moto import mock_dynamodb


@pytest.mark.describe("Lambdaの単体テスト")
class TestLambda:
    @pytest.mark.it("正常系")
    @mock_dynamodb()
    def test_lambda_handler_success(self, mock_table):
        # テスト対象をimport
        from functions.api import app
        importlib.reload(app)

        # mock_tableの戻り値であるcreate_authority_tableを実行
        # 前処理のほうではmock_tableを実行しておくだけなので
        # create_authority_tableはこちらでやる
        mock_table()

        # テストデータを用意
        event = {'body': {'id': '0001', 'name': 'satoh'}}

        # テスト対象を呼び出す
        data = app.lambda_handler(event)

        # 検証
        assert len(data) != 0
```
# 終わりに
今回は実行時のキャプチャーを省略しますが、mock_dynamodbの書き方を試してみました