I currently have the following basic Python class that I want to test:
class Example:
def run_steps(self):
self.steps = 0
while self.steps < 4:
self.step()
def step(self):
# some expensive API call
print("wasting time...")
time.sleep(1000)
self.steps += 1
As you can see, the step() method contains an expensive API call so I want to mock it with another function that avoids the expensive API call but still increments self.steps
. I found that this is possible by doing this (as seen from here):
def mock_step(self):
print("skip the wasting time")
self.steps += 1
# This code works!
def test(mocker):
example = Example()
mocker.patch.object(Example, 'step', mock_step)
example.run_steps()
I simply create a function called mock_step(self)
that avoids the API call, and I patch the original slow step()
method with the new mock_step(self)
function.
However, this causes a new problem. Since the mock_step(self)
function is not a Mock object, I can't call any of the Mock methods on it (such as assert_called() and call_count()):
def test(mocker):
example = Example()
mocker.patch.object(Example, 'step', mock_step)
example.run_steps()
# this line doesn't work
assert mock_step.call_count == 4
To solve this issue, I have tried to wrap mock_step
with a Mock object using the wraps
parameter:
def test(mocker):
example = Example()
# this doesn't work
step = mocker.Mock(wraps=mock_step)
mocker.patch.object(Example, 'step', step)
example.run_steps()
assert step.call_count == 4
but then I get a different error saying mock_step() missing 1 required positional argument: 'self'
.
So from this stage I am not sure how I can assert that step()
has been called exactly 4 times in run_steps()
.
There are several solutions to this, the simplest is probably using a standard mock with a side effect:
def mock_step(self):
print("skip the wasting time")
self.steps += 1
def test_step(mocker):
example = Example()
mocked = mocker.patch.object(Example, 'step')
mocked.side_effect = lambda: mock_step(example)
example.run_steps()
assert mocked.call_count == 4
side_effect
can take a callable, so you can both use a standard mock and the patched method.