I have a long-lived patch on a class, whose made instance undergoes multiple batches of assertions. Please see the below code snippet for the scenario.
It exposes (what I think is annoying) behavior in MagicMock.reset_mock
where it seemingly creates a new MagicMock
inside a sub-MagicMock
:
from unittest.mock import MagicMock
mock_cls = MagicMock()
mock_cls.return_value.method.side_effect = [5]
instance = mock_cls()
# Batch 1 of usage: uses side_effect
assert instance.method() == 5
mock_cls.reset_mock(return_value=True, side_effect=True)
# After this, mock_cls.return_value.method has a new id
# Batch 2 of usage: uses return_value
instance.return_value.method.return_value = 6
assert instance.method() == 6 # StopIteration
When run with Python 3.10.2, it raises a StopIteration
:
Traceback (most recent call last):
File "/path/to/code/play/quick_play.py", line 9, in <module>
assert instance.method() == 6
File "/path/to/.pyenv/versions/3.10.2/lib/python3.10/unittest/mock.py", line 1104, in __call__
return self._mock_call(*args, **kwargs)
File "/path/to/.pyenv/versions/3.10.2/lib/python3.10/unittest/mock.py", line 1108, in _mock_call
return self._execute_mock_call(*args, **kwargs)
File "/path/to/.pyenv/versions/3.10.2/lib/python3.10/unittest/mock.py", line 1165, in _execute_mock_call
result = next(effect)
StopIteration
Is it possible to use reset_mock
without creating new MagicMock
?
Alternately, how can I manually reset the side_effect
so the snippet runs?
Aside
What is the visited=None
argument for in reset_mock
's signature? It's undocumented in the 3.10 docs, and here
def reset_mock(self, visited=None,*, return_value=False, side_effect=False):
Sometimes in life, you answer your own question.
Alternate to reset_mock
From the side_effect
docs
If the function returns
DEFAULT
then the mock will return its normal value (from thereturn_value
).
from unittest.mock import MagicMock, DEFAULT
mock_cls = MagicMock()
mock_cls.return_value.method.side_effect = [5]
instance = mock_cls()
instance.method()
# Works, aligns with docs
mock_cls.return_value.method.side_effect = lambda: DEFAULT
instance.method() # <MagicMock name='mock().method()' ...>
# Alternately, this works too
mock_cls.return_value.method.side_effect = None
instance.method() # <MagicMock name='mock().method()' ...>
You can also find this used here and mentioned here.
Fixing My Snippet
There are two learnings here:
reset_mock
in favor of setting side_effect = None
return_value
for the 2nd batch was used incorrectlyreset_mock
on the made instance
from unittest.mock import MagicMock
mock_cls = MagicMock()
mock_cls.return_value.method.side_effect = [5]
instance = mock_cls()
# Batch 1 of usage: uses side_effect
assert instance.method() == 5
mock_cls.return_value.method.side_effect = None
# instance.reset_mock(side_effect=True) # Also works
# Batch 2 of usage: uses return_value
instance.method.return_value = 6 # Correct
# instance.return_value.method.return_value = 6 # Original incorrect
assert instance.method() == 6
Future readers: stay hungry, stay foolish.