【发布时间】:2021-12-28 06:31:03
【问题描述】:
我的一个固定装置有问题,它正在执行一个补丁,在测试调用之间没有重置。
fixture 基本上是一个封装对象的补丁,因此我可以断言它已被传递给另一个函数。
夹具如下所示:
@pytest.fixture
def mock_entities(mocker: MockFixture) -> MagicMock:
entities = Entities()
namespace = f"{__name__}.{Entities.__name__}"
return mocker.patch(namespace, return_value=entities)
Entities 是我想要修补的一个类,但我希望它完全像原来的那样运行,因为它具有property 方法以及使用__len__。它是在函数体中声明的,我需要模拟它的原因是因为我将它传递给另一个函数,并且我想断言它已正确传递。我最初尝试过“wraps=`,但我无法让它正常工作。
完整的测试代码如下:
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
在第二种测试方法中,fixture 正在修补Entities,它有一个return_value=Entities()(基本上)。但是,fixture/mock 似乎保留了第一次测试中的原始 Entities,这意味着它在 _entities 中已经有 2 条记录,因此总共有 8 条记录,而不是它应该有的 6 条。
> assert len(result.entities) == len(expected_entities_list)
E assert 8 == 6
E -8
E +6
为什么会这样?我认为在使用 pyest-mock 和 mocker 夹具时,无需重置模拟,因为它会为您处理这些问题
https://pypi.org/project/pytest-mock/
这个插件提供了一个 mocker 固定装置,它是 mock 包提供的修补 API 的一个瘦包装器。除了在测试结束后自动撤消模拟之外,它还提供了其他不错的实用程序,例如
spy和stub,并在比较调用时使用 pytest 内省。
这不会扩展到分配给return_value 的对象吗?如果这不是正确的方法,我应该怎么嘲笑Entities?
【问题讨论】:
标签: python mocking pytest pytest-mock