Mock side effect only X number of times
You mocked do_something()
. A mock replaces the original entirely; your choices are to either have the side effect (raise or return a value from the iterable) or to have the normal mock operations apply (returning a new mock object).
In addition, adding None
to the side_effect
sequence doesn't reset the side effect, it merely instructs the mock to return the value None
instead. You could add in mock.DEFAULT
instead; in that case the normal mock actions apply (as if the mock had been called without a side effect):
@mock.patch('tasks.do_something')def test_will_retry_until_successful(self, action): action.side_effect = [Exception("First"), Exception("Second"), Exception("Third"), mock.DEFAULT] tasks.task.delay() self.assert.... [stuff about task]
If you feel your test must end with calling the original, you'll have to store a reference to the original, unpatched function, then set the side_effect
to a callable that will turn around and call the original when the time comes:
# reference to original, global to the test module that won't be patchedfrom tasks import do_somethingclass TaskTests(test.TestCase): @mock.patch('tasks.do_something') def test_will_retry_until_successful(self, action): exceptions = iter([Exception("First"), Exception("Second"), Exception("Third")]) def side_effect(*args, **kwargs): try: raise next(exceptions) except StopIteration: # raised all exceptions, call original return do_something(*args, **kwargs) action.side_effect = side_effect tasks.task.delay() self.assert.... [stuff about task]
I cannot, however, foresee a unittesting scenario where you'd want to do that. do_something()
is not part of the Celery task being tested, it is an external unit, so you should normally only test if it was called correctly (with the right arguments), and the correct number of times.