Search code examples
pythonamazon-web-servicespytestboto3moto

Moto does not appear to be mocking aws interactions in a pytest


Say I want to mock the following:

session = boto3.Session(profile_name=profile)
resource = session.resource('iam')
iam_users = resource.users.all()
policies = resource.policies.filter(Scope='AWS', OnlyAttached=True, PolicyUsageFilter='PermissionsPolicy')

How do I go about starting to mock this with in pytest? I could create mocked objects by creating a dummy class and the necessary attributes, but I suspect that's the wrong approach.

Some additional details, here's what I'm trying to test out:

def test_check_aws_profile(self, mocker):
    mocked_boto3 = mocker.patch('myapp.services.utils.boto3.Session')
    mocker.patch(mocked_boto3.client.get_caller_identity.get, return_value='foo-account-id')
    assert 'foo-account-id' == my_func('foo')

#in myapp.services.utils.py
def my_func(profile):
    session = boto3.Session(profile_name=profile)
    client = session.client('sts')
    aws_account_number = client.get_caller_identity().get('Account')
    return aws_account_number

But I can't quite seem to be able to get this patched correctly. I'm trying to make it so that I can patch session and the function calls in that method

I tried using moto and got this:

@mock_sts
def test_check_aws_profile(self):
    session = boto3.Session(profile_name='foo')
    client = session.client('sts')
    client.get_caller_identity().get('Account')

But I'm running into

>           raise ProfileNotFound(profile=profile_name)
E           botocore.exceptions.ProfileNotFound: The config profile (foo) could not be found

So it seems like it's not mocking anything :|

Edit:

Turns out you need to have the mocked credentials in a config and credentials file for this to work.


Solution

  • I'm not sure what exactly you want, so I'll give you something to start.

    You let unittest.mock to mock everything for you, for example. (Useful reading: https://docs.python.org/3/library/unittest.mock.html)

    module.py:

    import boto3
    
    def function():
        session = boto3.Session(profile_name="foobar")
        client = session.resource("sts")
        return client.get_caller_identity().get('Account')
    

    test_module.py:

    from unittest.mock import patch
    
    import module
    
    @patch("module.boto3")  # this creates mock which is passed to test below
    def test_function(mocked_boto):
        # mocks below are magically created by unittest.mock when they are accessed
        mocked_session = mocked_boto.Session()
        mocked_client = mocked_session.resource()
        mocked_identity = mocked_client.get_caller_identity()
    
        # now mock the return value of .get()
        mocked_identity.get.return_value = "foo-bar-baz"
    
        result = module.function()
        assert result == "foo-bar-baz"
    
        # we can make sure mocks were called properly, for example
        mocked_identity.get.assert_called_once_with("Account")
    
    

    Results of pytest run:

    $ pytest
    ================================ test session starts ================================
    platform darwin -- Python 3.7.6, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
    rootdir: /private/tmp/one
    collected 1 item                                                                    
    
    test_module.py .                                                              [100%]
    
    ================================= 1 passed in 0.09s =================================
    

    I would also recommend to install pytest-socket and run pytest --disable-socket to make sure your tests do not talk with network by accident.