Stop Iteration Error
- I have the following
TestGenerateAaliDictclass that contains 2 tests. - Each of the test contained in the class will pass if run individually (comment the others)
- Whenever I run them together, regardless of how I arrange the order, the first test will pass and the second will throw a
StopIterationerror. - This
StopIterationerror I get is commonly triggered when a side effect is called more thanlen(side_effect). - It seems mocks are leaking from one test to another. Even though I tried with resetting mocks at the end of each test with
reset_mock():
# Reset
children_mock.reset_mock()
aali_get_mock.reset_mock()
and with manual resets:
# Reset
children_mock.side_effect = mocker.DEFAULT
children_mock.return_value = mocker.DEFAULT
aali_get_mock.side_effect = mocker.DEFAULT
aali_get_mock.return_value = mocker.DEFAULT
Here are the test files:
from pytest_mock import MockerFixture
from model_bakery import baker
from django_mock_queries.query import MockSet, MockModel
from areas.utils.logic import generate_aali_dict
from areas.models import AALI
class TestGenerateAaliDict:
aali_1 = baker.prepare(AALI, aalia=None, id=1)
aali_2 = baker.prepare(AALI, aalia=aali_1, id=2, aalia_id=aali_1.id)
aali_3 = baker.prepare(AALI, aalia=aali_2, id=3, aalia_id=aali_2.id)
aali_4 = baker.prepare(AALI, aalia=aali_3, id=4, aalia_id=aali_3.id)
aali_5 = baker.prepare(AALI, aalia=aali_4, id=5, aalia_id=aali_4.id)
def test_aali_has_only_children(self, mocker: MockerFixture):
# Mock
children_mock = mocker.Mock()
children_mock.return_value.return_value.count.side_effect = [1] * 4 + [0]
mocker.patch(
'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager',
children_mock
)
aali_get_mock = mocker.Mock()
aali_get_mock.get.side_effect = (self.aali_1, self.aali_2, self.aali_3, self.aali_4, self.aali_5)
mocker.patch(
'areas.models.AALI.objects',
aali_get_mock
)
# Assert
assert generate_aali_dict(1) == {
"aali-1": 1,
"aali-2": 2,
"aali-3": 3,
"aali-4": 4,
"aali-5": 5,
}
def test_aali_has_only_parent(self, mocker: MockerFixture):
# Mock
aali_get_mock = mocker.Mock()
aali_get_mock.get.return_value = self.aali_5
mocker.patch(
'areas.models.AALI.objects',
aali_get_mock
)
children_mock = mocker.Mock()
children_mock.return_value.return_value.count.return_value = 0
mocker.patch(
'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager',
children_mock
)
# Assert
assert generate_aali_dict(5) == {
"aali-1": 1,
"aali-2": 2,
"aali-3": 3,
"aali-4": 4,
"aali-5": 5,
}
def test_aali_has_parent_and_children(self, mocker):
# Mock
children_mock = mocker.Mock()
children_mock.return_value.return_value.count.side_effect = [1] * 2 + [0]
mocker.patch(
'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager',
children_mock
)
aali_get_mock = mocker.Mock()
aali_get_mock.get.side_effect = (self.aali_3, self.aali_4, self.aali_5)
mocker.patch(
'areas.models.AALI.objects',
aali_get_mock
)
# Assert
assert generate_aali_dict(3) == {
"aali-1": 1,
"aali-2": 2,
"aali-3": 3,
"aali-4": 4,
"aali-5": 5,
}
Here's the full traceback:
_____________________________________________________________________ TestGenerateAaliDict.test_aali_has_only_parent ______________________________________________________________________
self = <test_areas.test_utils.TestGenerateAaliDict object at 0x7fe559819db0>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7fe559255150>
def test_aali_has_only_parent(self, mocker: MockerFixture):
# Mock
aali_get_mock = mocker.Mock()
aali_get_mock.get.return_value = self.aali_5
mocker.patch(
'areas.models.AALI.objects',
aali_get_mock
)
children_mock = mocker.MagicMock()
children_mock.return_value.return_value.count.return_value = 0
mocker.patch(
'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager',
children_mock
)
# Assert
> assert generate_aali_dict(5) == {
"aali-1": 1,
"aali-2": 2,
"aali-3": 3,
"aali-4": 4,
"aali-5": 5,
}
tests/test_areas/test_utils.py:56:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
areas/utils/logic.py:15: in generate_aali_dict
while aali.children.count() > 0:
/usr/local/lib/python3.10/unittest/mock.py:1104: in __call__
return self._mock_call(*args, **kwargs)
/usr/local/lib/python3.10/unittest/mock.py:1108: in _mock_call
return self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='mock()().count' id='140623019748960'>, args = (), kwargs = {}, effect = <list_iterator object at 0x7fe559240d00>
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
elif not _callable(effect):
> result = next(effect)
E StopIteration
/usr/local/lib/python3.10/unittest/mock.py:1165: StopIteration
==================================================================================== warnings summary =====================================================================================
../usr/local/lib/python3.10/site-packages/django/utils/version.py:6
/usr/local/lib/python3.10/site-packages/django/utils/version.py:6: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives
from distutils.version import LooseVersion
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================================= short test summary info =================================================================================
FAILED tests/test_areas/test_utils.py::TestGenerateAaliDict::test_aali_has_only_parent - StopIteration
I would love try to do this with context managers, but I don't know how to get away with using them for two nested mocks per test like I'm using right now.
Hi @Lucasmiguelmac,
Whenever I run them together, regardless of how I arrange the order, the first test will pass and the second will throw a StopIteration error.
Didn't look at the example too deeply, but this suggests some global state is leaking from one test to the other. Glancing at your code, this instances are being shared between tests:
class TestGenerateAaliDict:
aali_1 = baker.prepare(AALI, aalia=None, id=1)
aali_2 = baker.prepare(AALI, aalia=aali_1, id=2, aalia_id=aali_1.id)
aali_3 = baker.prepare(AALI, aalia=aali_2, id=3, aalia_id=aali_2.id)
aali_4 = baker.prepare(AALI, aalia=aali_3, id=4, aalia_id=aali_3.id)
aali_5 = baker.prepare(AALI, aalia=aali_4, id=5, aalia_id=aali_4.id)
I suggest to move them to an autouse fixture, so you get new fresh instances for each test:
class TestGenerateAaliDict:
@pytest.fixture(autouse=True)
def setup_aali(self):
self.aali_1 = baker.prepare(AALI, aalia=None, id=1)
self.aali_2 = baker.prepare(AALI, aalia=aali_1, id=2, aalia_id=aali_1.id)
self.aali_3 = baker.prepare(AALI, aalia=aali_2, id=3, aalia_id=aali_2.id)
self.aali_4 = baker.prepare(AALI, aalia=aali_3, id=4, aalia_id=aali_3.id)
self.aali_5 = baker.prepare(AALI, aalia=aali_4, id=5, aalia_id=aali_4.id)
Hi @nicoddemus. Unfotunately this did not change anything. The state that is indeed leaking, I suspect comes from the mock objects. Since this is an error that happens when a side_effect was called more times than expected.
What would fix this I think, is try to mock inside a context manager. But this mocks I'm using are rather complex in the sense they are really nested.
Not sure what else to try. :/
However declaring the backer.prepare instances at the class level like it was being done is probably not what you want.