Stubber#
Boto3 comes with the Stubber class, but it doesn’t work well for all calls. In those cases
Moto#
Moto is a Python package that allows for easy mocking of AWS services. The canonical way to do this is to use the @mock_<service> decorator outside of your test function and call boto3 functions there. Moto will detect and intercept the boto3 calls. Code example from the Moto documentation:
import boto3
from moto import mock_s3
from mymodule import MyModel
@mock_s3
def test_my_model_save:
conn = boto3.resource('s3', region_name='us-east-1')
# We need to create the bucket since this is all in Moto's 'virtual' AWS account
conn.create_bucket(Bucket='mybucket')
model_instance = MyModel('steve', 'is awesome')
model_instance.save
body = conn.Object('mybucket', 'steve').get[
'Body'].read.decode("utf-8")
assert body == 'is awesome'
Patching Clients and Resources#
This is kind of useless for most testing, where there is an external module or package under test. The Moto documentation advises users to set up their boto3 clients and/or resources within the functions that use them, but this isn’t always workable. Instead, since you’re already importing the module or package under test, you can use Moto’s patch_client or patch_resource to patch the instantiated client or resource:
import mymodule
from moto import mock_s3
@mock_s3
def test_get_s3_objects:
patch_client(mymodule.mymodule.s3_client)
mymodule.mymodule.s3_client.create_bucket(Bucket="s3-bucket")
print(mymodule.mymodule.s3_client.list_buckets["Buckets"])
downloaded_objects = mymodule.mymodule._get_s3_objects("test")
assert downloaded_objects == expected_objects
Moto with Fixtures#
If you use Pytest, here is how to set up Moto mocks that mock external clients in fixtures:
- Make sure the module under test is imported (it has to be for the test function anyways).
- If the client or resource in the module under test is set up in the outermost scope, use a fixture with function scope that yields a
patch_clientorpatch_resourcewithin themock_<service>context manager. - The above should be input to the fixture that does the set up for the different test cases.
- The mock client or resource needs to be set up. e.g. for S3 the expected buckets and items need to be created
- The test method(s) don’t need to do anything special beyond calling the functions under test.
from moto import mock_s3
import module_under_test
## Step 2
@pytest.fixture(scope="function")
def set_up_s3:
with mock_s3:
yield patch_client(module_under_test.s3_client)
## Steps 3 & 4
@pytest.fixture(params=["success", "fail"])
def generate_s3_function_parameters(request, mocker, set_up_s3):
test_input = "test input"
if request.param == "success":
expected_output = []
module_under_test.s3_client.create_bucket(Bucket="s3-bucket")
print(module_under_test.s3_client.list_buckets["Buckets"])
return test_input, expected_output
Sources#
Stubber on Stack Overflow Moto documentation Yielding a mocked client from a Pytest fixture