I'm trying to write test for this function. I'd like not to rely on real gcs object, but mock the objects.
# gcs_blobs.py file
from google.cloud import storage
def check_existing_blobs(new_blob_names: list[str], bucket: storage.Bucket) -> list[storage.Blob]:
# Check if any of the new blobs exists already on bucket
current_blobs = [blob for blob in bucket.list_blobs()]
existing_blobs = []
for blob_name in new_blob_names:
for blob in current_blobs:
if blob_name == blob.name:
existing_blobs.append(blob)
return existing_blobs
As I understand correctly, I should
I'm trying to do that with no success. In test code below, check_existing_blobs(new_blob_names, bucket)
returns empty list. In this case I'd like it to return blob with name blob1.
How do I do that properly?
Would using 'side_effect' on blob.name allow me to return names from the provided 'existing_blob_names' list in consecutive calls to blob.name?
How to do this assertion properly, with list of blobs with names from the list?
# test_blobs.py file
from unittest.mock import Mock, MagicMock
import pytest
from google.cloud import storage
from .gcs_files import (
check_existing_blobs
)
@pytest.mark.parametrize(
"new_blob_names,existing_blob_names",
[
(
['blob1'],
['blob1', 'blob2'],
),
]
)
def test_contains_existing_files(mocker, new_blob_names, existing_blob_names):
storage.Bucket = MagicMock()
bucket = storage.Bucket()
storage.Blob = MagicMock()
blob = storage.Blob()
#mocker.patch.object(blob, "name", new="blob_name") # after that blob.name prints "blob_name"
mocker.patch.object(blob, "name", side_effect=existing_blob_names)
print(blob.name) # output: <MagicMock name='name' id='139862698975120'>
# in place of '???' it should be mock of storage.Blob instance which .name returns one of the blob names
# assert check_existing_blobs(new_blob_names, bucket) == [???]
Also please let me know if I should split this question to subproblems.
I'd recommend not using MagicMock()
for this. Instead, just make a new class that has the behavior you want:
class MockBucket:
def __init__(self, names):
self.blobs = [MockBlob(x) for x in names]
def list_blobs(self):
return self.blobs
class MockBlob:
def __init__(self, name):
self.name = name
I'd also recommend not assigning to storage.Bucket
or storage.Blob
. These assignments won't be reverted when the test function ends, so they have great potential to interfere with later tests. In fact, the purpose of mocker.patch
and mocker.patch.object
is to avoid this kind of assignment. In your case, though, there's no reason to patch anything, because the code being tested doesn't rely on any global variables.