up:: [[AWS MOC]] # Strategies for Mocking AWS Services for Testing ## 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: ```python 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: ```python 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(fslib.fslib.s3_client.list_buckets()["Buckets"]) downloaded_objects = mymodule.mymodule._get_s3_objects("test") assert downloaded_objects == expected_objects ``` ### Moto with Fixtures If you use [[Testing Python Code with Pytest|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. ```python 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 ![[MLE-25 - Add support for storing video features with fslib#^3h7rpq]] [Stubber on Stack Overflow](https://stackoverflow.com/questions/37143597/mocking-boto3-s3-client-method-python) [Moto documentation](https://docs.getmoto.org/en/latest/docs/getting_started.html) ![[MLE-25 - Add support for storing video features with fslib#^w80t7g]] [Yielding a mocked client from a Pytest fixture](https://github.com/spulec/moto/issues/1568#issuecomment-392461010) ![[MLE-25 - Add support for storing video features with fslib#^r41ptc]]