I have a issue with one of my fixtures, which is doing a patch not resetting between test calls.
The fixture is basically a patch which is wrapper around a object, so I can assert that it has been passed into another function.
The fixture looks like this:
@pytest.fixture
def mock_entities(mocker: MockFixture) -> MagicMock:
entities = Entities()
namespace = f"{__name__}.{Entities.__name__}"
return mocker.patch(namespace, return_value=entities)
Entities
is a class that I want to patch, but I want it to function completely like the original, due to the fact that it has property
methods, as well as using __len__
. It is declared in the body of a function and the reason I need it mocked is because I'm passing it into another function and I want to assert that it has been passed in correctly. I originally tried "wraps=`, but I wasn't able to get that to work correctly.
Full test code below:
import pytest
from pytest_mock import MockFixture
from unittest.mock import MagicMock, PropertyMock
from typing import List
from pprint import pprint
from unittest.mock import patch
class Entities:
_entities: List[dict] = []
def __init__(self, entities: List[dict] = []):
self._entities = entities
@property
def entities(self) -> List[dict]:
return self._entities
@entities.setter
def entities(self, value: List[dict]):
self._entities = value
def append(self, value: dict):
self._entities.append(value)
def __len__(self) -> int:
return len(self._entities)
class ApiClient:
def get_values(self) -> List[dict]:
# We get values from a API with a pager mechanism here
pass
class EntitiesCacheClient:
def get_values(self) -> Entities:
# We get values from cache here
pass
def set_values(sel, values: Entities):
# We set values to cache here
pass
class EntityDataSource:
_api_client: ApiClient = None
_cache_client: EntitiesCacheClient = None
def __init__(self) -> None:
self._api_client = ApiClient()
self._cache_client = EntitiesCacheClient()
def get_entities(self) -> Entities:
entities = self._get_entities_from_cache()
if entities:
return entities
# I want to mock Entities, so that I can assert that it is passed in to the EntitiesCacheClient.set_values()
entities = Entities()
api_values = 1
while api_values:
api_values = self._api_client.get_values()
if not api_values:
break
for values in api_values:
entities.append(values)
if entities:
self._save_entities_to_cache(entities)
return entities
def _get_entities_from_cache(self) -> Entities:
return self._cache_client.get_values()
def _save_entities_to_cache(self, entities: Entities):
self._cache_client.set_values(entities)
@pytest.fixture
def mock_entities_cache_client(mocker: MockFixture) -> MagicMock:
namespace = f"{__name__}.{EntitiesCacheClient.__name__}"
return mocker.patch(namespace, autospec=True).return_value
@pytest.fixture
def mock_api_client(mocker: MockFixture) -> MagicMock:
namespace = f"{__name__}.{ApiClient.__name__}"
return mocker.patch(namespace, autospec=True).return_value
@pytest.fixture
def mock_entities(mocker: MockFixture) -> MagicMock:
entities = Entities()
namespace = f"{__name__}.{Entities.__name__}"
return mocker.patch(namespace, return_value=entities)
def test_entity_data_source_entities(mock_entities_cache_client, mock_api_client, mock_entities):
mock_entities_cache_client.get_values.return_value = None
expected_entity_1 = {"id": 1, "data": "Hello"}
expected_entity_2 = {"id": 2, "data": "World"}
expected_entities_list = [
expected_entity_1, expected_entity_2
]
mock_api_client.get_values.side_effect = [
[
expected_entity_1,
expected_entity_2,
],
[]
]
entity_data_source = EntityDataSource()
result: Entities = entity_data_source.get_entities()
mock_entities_cache_client.set_values.assert_called_once_with(mock_entities.return_value)
assert len(result.entities) == len(expected_entities_list)
assert result.entities == expected_entities_list
def test_entity_data_source_entities_more_results(mock_entities_cache_client, mock_api_client, mock_entities):
mock_entities_cache_client.get_values.return_value = None
expected_entity_1 = {"id": 1, "data": "Hello"}
expected_entity_2 = {"id": 2, "data": "World"}
expected_entity_3 = {"id": 3, "data": "How"}
expected_entity_4 = {"id": 4, "data": "Are"}
expected_entity_5 = {"id": 5, "data": "You"}
expected_entity_6 = {"id": 6, "data": "Doing?"}
expected_entities_list = [
expected_entity_1, expected_entity_2, expected_entity_3,
expected_entity_4, expected_entity_5, expected_entity_6
]
mock_api_client.get_values.side_effect = [
[
expected_entity_1,
expected_entity_2,
expected_entity_3,
expected_entity_4,
expected_entity_5,
],
[expected_entity_6],
[]
]
entity_data_source = EntityDataSource()
result: Entities = entity_data_source.get_entities()
mock_entities_cache_client.set_values.assert_called_once_with(mock_entities.return_value)
assert len(result.entities) == len(expected_entities_list)
assert result.entities == expected_entities_list
On the second test method, the fixture is patching Entities
and it has a return_value=Entities()
(basically). However, the fixture/mock seems to retain the original Entities
from the first test, meaning that it already has 2 records inside the _entities
, resulting in a total of 8 records and not the 6 it should have.
> assert len(result.entities) == len(expected_entities_list)
E assert 8 == 6
E -8
E +6
Why is this happening? I thought when using the pyest-mock
and the mocker
fixture, there would be no need to reset the mocks as it takes care of that for you
https://pypi.org/project/pytest-mock/
This plugin provides a mocker fixture which is a thin-wrapper around the patching API provided by the mock package. Besides undoing the mocking automatically after the end of the test, it also provides other nice utilities such as
spy
andstub
, and uses pytest introspection when comparing calls.
Does this not extend to the objects assigned to return_value
? How am I supposed to be mocking Entities
if this is not the correct way?