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:

  1. Make sure the module under test is imported (it has to be for the test function anyways).
  2. 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_client or patch_resource within the mock_<service> context manager.
  3. The above should be input to the fixture that does the set up for the different test cases.
  4. The mock client or resource needs to be set up. e.g. for S3 the expected buckets and items need to be created
  5. 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