💭

boto3で特定のメソッドをmockする

2022/12/30に公開

やりたいこと

Pythonのユニットテストで、boto3クライアントの特定メソッド(だけ)をモックしたいときがあります。こちらのstackoverflowに良さそうのやり方があったのでメモ。解はいろいろありそうですが、そのうち2つに関して書きます。(※motoは使わない)

https://stackoverflow.com/questions/37143597/mocking-boto3-s3-client-method-python/

方法1. botocoreのstubを使う

boto3のドキュメントよりコードをコピペ

import datetime
import botocore.session
from botocore.stub import Stubber


s3 = botocore.session.get_session().create_client('s3')

response = {
    "Owner": {
        "ID": "foo",
        "DisplayName": "bar"
    },
    "Buckets": [{
        "CreationDate": datetime.datetime(2016, 1, 20, 22, 9),
        "Name": "baz"
    }]
}


with Stubber(s3) as stubber:
    stubber.add_response('list_buckets', response, {})
    service_response = s3.list_buckets()

assert service_response == response
  • s3のlist_bucketsをmockしている例
  • with句でなくても可能。ない場合はstubber.activate()が必要
  • 上記では空の{}になっておりs3.list_buckets()は引数なし。expected_params = {'Bucket': 'test-bucket'}stubber.add_responseに与えると、その値のときだけmockする。expected_params = {'Bucket': ANY}とすると、s3.list_buckets()に何が入ってもよいことになる
expected_params = {'Bucket': ANY}
stubber.add_response('list_objects', response, expected_params)

with stubber:
    service_response = s3.list_objects(Bucket='test-bucket')

assert service_response == response

方法2. boto3の_make_api_callを差し替える

上記Stackoverflowで紹介されている方法です。同じ人が別の質問に書いている回答もあります。コードは前者からコピペ。

import botocore
from botocore.exceptions import ClientError
from mock import patch
import boto3

orig = botocore.client.BaseClient._make_api_call

def mock_make_api_call(self, operation_name, kwarg):
    if operation_name == 'UploadPartCopy':
        parsed_response = {'Error': {'Code': '500', 'Message': 'Error Uploading'}}
        raise ClientError(parsed_response, operation_name)
    return orig(self, operation_name, kwarg)

with patch('botocore.client.BaseClient._make_api_call', new=mock_make_api_call):
    client = boto3.client('s3')
    # Should return actual result
    o = client.get_object(Bucket='my-bucket', Key='my-key')
    # Should return mocked exception
    e = client.upload_part_copy()
  • s3のupload_part_copyをmockしている例
  • boto3.clientが、_make_api_callというメソッドを使ってAPIを呼んでいることに注目して、その部分を差し替える
  • def mock_make_api_call(self, operation_name, kwarg):で、特定の操作のときにだけ差し替えます(ここではoperation_name == 'UploadPartCopy'のときraise ClientErrorに差し替え)。それ以外は、orig()を返すのでそのままになる
  • with patchを使って _make_api_callを mock_make_api_callでモックすると、 client.upload_part_copy()のときは、 operation_name == 'UploadPartCopy'がTrueなのでモックされる

まとめ

  • boto3クライアントの特定メソッドをmockする方法について書きました
  • どちらが良いかというのはケースバイケースですが、個人的に解りやすかったので方法2を使いました

Discussion