Search code examples
pythonunit-testingmockingmockitoattributeerror

AttributeError on Mocked Class in Python


I am using mockito to unit test a program in Python. I have a class like:

import boto3
import datetime

class Cache:

    def __init__(self):
        client = boto3.resource('s3')
        self.bucket_name = 'name'
        self.bucket = client.Bucket(self.bucket_name)

    def setup_cache(self, cache_file='cache.csv', cache_filepath='cache'):
        cache_object = self.bucket.Object(cache_file)

        if cache_object.last_modified < datetime.datetime.now():
            self.bucket.download_file(cache_filepath, cache_file)
        else:
            print('Cache already up to date')


def main():
    cache = Cache()
    cache.setup_cache()

And the test code I am getting stuck on is this:

from mockito import mock, when
import datetime
import boto3

import mock_cache

class TestMockCache:

    def test_last_mod(self):

        mock_client = mock()
        when(boto3).resource('s3').thenReturn(mock_client)

        mock_bucket = mock()
        when(mock_client).Bucket('name').thenReturn(mock_bucket)

        mock_bucket.last_modified = datetime.datetime.now()

        mock_cache.main()

When running pytest on the unit test, I am getting thrown this attribute error:

AttributeError: 'NoneType' object has no attribute 'last_modified'

From the documentation it looked like I could assign 'cache_mock.last_modified' like this. However, I also tried:

when(cache_mock).last_modified.thenReturn(test_date)

and got:

AttributeError: 'StubbedInvocation' object has no attribute 'thenReturn'

Which I don't fully understand, but assume that means a mockito mock() object can't have multiple return values?

Any help with this would be appreciated. I feel like I am misunderstanding something fundamental about either how mockito's mock works or mocking in general.


Solution

  • The patch from the Mock can be used to mock the client from boto3. Do not have to return anything from the client, as it is being mocked on the whole.

    e.g.:

    from folder.file import your_func
    from unittest.mock import patch
    
    class TestSomething(unittest.TestCase):
        @patch("botocore.client.BaseClient._make_api_call")
        def test_something(self, _mock_client_boto3):
            your_func()
            .. do something
    

    In case you want to return something from the client, it could be achieved by specifying the return_value in the patch as shown below:

    from folder.file import your_func
    from unittest.mock import patch
    
    class TestSomething(unittest.TestCase):
        @patch("botocore.client.BaseClient._make_api_call", return_value=None)
        def test_something(self, _mock_client_boto3):
            your_func()
            .. do something