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()
.
Advertisement
Answer
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.