'How do I patch a class attribute in pytest?
I have a service class that connections to AWS S3. The connection uses boto3 within the __init__() method. I would like to mock this to use a moto s3 instance I've defined in a fixture, but I just can't get the mock to do anything.
Let's say I have a service class that looks like this:
import boto3
class S3Storage:
def __init__(self):
self._s3 = boto3.resource('s3')
def do_download(self):
self._s3 .download_file(
Key='file.txt',
Bucket='mybucket',
Filename='path/to/destination/file.txt',
)
and then I create a conftest file that has these moto fixtures:
# Fixtures
@pytest.fixture(scope='function')
def mocked_s3r():
with mock_s3():
yield boto3.resource('s3')
@pytest.fixture(scope='function')
def mocked_s3client():
with mock_s3():
yield boto3.client('s3')
@pytest.fixture(scope='function', autouse=True)
def upload_s3_resources(mocked_s3client, s3files):
mocked_s3client.create_bucket(Bucket='mybucket')
mocked_s3client.upload_file(
Filename='path/to/destination/file.txt',
Bucket='mybucket',
Key='file.txt',
)
The bottom fixture will grab a local file and place it in the moto s3 instance, which can be accessed from the mocked_s3r client mock.
My problem is that I cannot make a successful patch for the S3Storage._s3 attribute that holds the boto resource (I know I'm mixing boto clients and resources here, but I don't think that's causing the issue).
So I tried writing some fixtures to patch (using pytest-mock) or monkeypatch the boto resource and/or client.
# This is what I can't make work...
@pytest.fixture(autouse=True)
def mocked_s3(mocked_s3client, mocker):
mocker.patch('app.utils.s3_storage.boto3.resource', return_value=mocked_s3r)
return mocked_s3client
# This other approach also doesn't work...
@pytest.fixture(autouse=True)
def mocked_s3(mocked_s3client, mocker):
mocker_s3storage = mocker.patch('app.utils.s3_storage.boto3.resource')
mocker_s3storage()._s3 = mocked_s3client
return mocked_s3client
# Nor this...
@pytest.fixture(autouse=True)
def mocked_s3(mocked_s3client, monkeypatch):
monkeypatch.setattr('app.utils.s3_storage.S3Storage._s3', mocked_s3client)
return mocked_s3client
But nothing works. I think I might be fundamentally misunderstanding how to patch an attribute that belongs to an instance of a class.
I'd rather do all this in a fixture, not in each individual test, such that I can write a test like:
def test_download_file(mocked_s3client):
s3storage = S3Storage()
s3storage._s3 # This should be a mock object, but it just connects to the real AWS
s3storage.do_download()
and I don't have to specify the mock each time.
Solution 1:[1]
It is not necessary to patch the client, in order for Moto to work. As long as clients/resources are created while the mock is active, they are automatically patched.
Using your example fixtures, the following test works:
def test_file_exists(upload_s3_resources):
S3Storage().do_download()
test_file_exists() # todo: actually verify somethign happened
Note that the download-file call in your logic should be slightly modified, but I'm assuming that was just a example to keep things simple.
I had to change to the following to get the test to succeed:
self._s3.Bucket('mybucket').download_file('file.txt', 'test.txt')
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Bert Blommers |
