Search code examples
pythonpytestboto3moto

How to mock hard dependency using moto library


I am trying to use moto to mock acm client in a python code but it doesn't seem to work. Coming from a dependency injection mindset this idea of mock decorator isn't clear to me.

def handle_custom_resource_request(event, context):
        try:
            cert_arn = event['PhysicalResourceId']
            acm_client.delete_certificate(CertificateArn=cert_arn)
        except Exception as e:
            pass
        finally:
            pass
@mock_acm
def test_handle_custom_resource_request():
    event = {
        'RequestType': 'Delete',
        'PhysicalResourceId': 'arn:aws:acm:eu-west-1:123456789:certificate/DummyCertificate',
        'ResponseURL': 'http://localhost'
    }
    context = {}
    response = cert_requester.handle_custom_resource_request(event, context)
    print(response)

I want to mock this acm client that it should return a value of True for example when the delete_certificate method is called. However it seems like there is no mocking happening in this test and I am getting the following error

botocore.exceptions.ClientError: An error occurred (ExpiredTokenException) when calling the DeleteCertificate operation: The security token included in the request is expired

Is there way to mock return values like we have in other languages testing frameworks.


Solution

  • To avoid your tests reaching out to AWS, the mock has to be started before any boto3-clients are created.

    From your example code, the easiest way would be to simply move the imports around:

    @mock_acm
    def test_handle_custom_resource_request():
        import cert_requester
        event = {
            'RequestType': 'Delete',
            'PhysicalResourceId': 'arn:aws:acm:eu-west-1:123456789:certificate/DummyCertificate',
            'ResponseURL': 'http://localhost'
        }
        context = {}
        response = cert_requester.handle_custom_resource_request(event, context)
        print(response)
    
    

    The documentation has a bit more information on this: http://docs.getmoto.org/en/latest/docs/getting_started.html#what-about-those-pesky-imports


    I want to mock this acm client that it should return a value of True for example when the delete_certificate method is called.

    Think of Moto as a local, offline equivalent of AWS. The moment you have a unit test that passes against AWS, using boto3-requests to verify it does what it's supposed to do, you can slap the mock_..-decorator on it. With the decorator on it, you can trust that the tests still verify that the behaviour is correct, but without the cost of running it against AWS.

    For example, if you want to know whether a certificate was successfully deleted, you can use the following boto3-requests:

    • Call describe_certificate, and verify it throws an error (because it no longer exists)
    • Call list_certificates, and verify the deleted certificate no longer shows up

    So the final test could look something like this:

    def test_handle_custom_resource_request():
        # GIVEN
        # A certificate exists
    
        # WHEN
        # Our business logic should delete the certificate with that ARN
        import cert_requester
        event = {
            'RequestType': 'Delete',
            'PhysicalResourceId': 'arn:aws:acm:eu-west-1:123456789:certificate/DummyCertificate',
            'ResponseURL': 'http://localhost'
        }
        context = {}
        response = cert_requester.handle_custom_resource_request(event, context)
        print(response)
    
        # THEN
        # the certificate should be deleted
        certificates = boto3.client("acm").list_certificates()
        assert "DummyCertificate" not in certificates