tenacity icon indicating copy to clipboard operation
tenacity copied to clipboard

Disable `wait` for unittest

Open DanEEStar opened this issue 8 years ago • 17 comments

I am using the @retry decorator on a function which makes an HTTP-request.

I am using this exact decorator call as an example:

@retry(stop=stop_after_attempt(7), wait=wait_random_exponential(multiplier=1, max=60))
def func():
   ...
   requests.post(...)

I have a unit tests which tests, that func does indeed get called multiple times when the post-request fails.

But it is a bit annoying that the test takes a long time.

Is it possible to disable the wait time somehow only in the unit test to make the test faster? Do I have to mock a specific function?

I also posted this on stackoverflow, in case you want some points :) https://stackoverflow.com/questions/47906671/python-retry-with-tenacity-disable-wait-for-unittest

DanEEStar avatar Dec 20 '17 13:12 DanEEStar

You can change the wait function temporarily in your test:

func.retry.wait = wait_none

Using mock for example, that should be easy to make sure it's then restored to the original.

jd avatar Dec 20 '17 13:12 jd

@DanEEStar the way I handle time at least in synchronous tests is that I use a datetime-mock library, my preferred one is freezegun but feel free to use any other. The trick is that there's usually a function to advance the frozen clock manually, for freezegun it is

frozen_datetime.tick(delta=datetime.timedelta(seconds=10))

With that in mind I mock time.sleep function not to wait for wallclock time to elapse but rather to advance the frozen clock. This kills two birds with one stone: sleep returns right away and the tests don't get stuck for a long time, but you also get the time invariant back, that is after you invoke time.sleep datetime.now() returns a timestamp in the future. It could be slightly more involved for asynchronous tasks where the time can affect some sort of an event loop, but it's not impossible.

immerrr avatar Jul 09 '18 12:07 immerrr

We would also appreciate some specific docs on how to mock out delays in running unit tests. Our current approach just got way more complicated https://review.openstack.org/#/c/596471

steveb avatar Aug 28 '18 00:08 steveb

After working on this for a while I went with func.retry.stop = stop_after_attempt(1)

josephbosire avatar Sep 11 '18 15:09 josephbosire

If anyone finds this now, I dug into @steveb's code and found that in a later pull request it looks like you found an even simpler way of doing this:

https://github.com/openstack/tripleo-common/commit/eba4912fc4cd37442ce4603544bb070612a1df74#diff-f02c888d3d91763350bb6b11c12bf0ffR167

In short, if you decorate a function func with @retry, in the test you can do:

func.retry.sleep = mock.Mock()

And that makes the retry calls happen immediately.

I whipped up a gist that demonstrates Steve's method:

https://gist.github.com/davidscolgan/39433d2de29ea1282bbecaf5afd73900

dvcolgan avatar Dec 26 '18 16:12 dvcolgan

@davidscolgan Thank you

Gatsby-Lee avatar Jul 25 '19 05:07 Gatsby-Lee

func.retry.sleep = mock.Mock()

Unfortunately, you don't always have an access to the function in a unit test. I'd rather go with the proposal in #228.

dtantsur avatar May 11 '20 09:05 dtantsur

I prefer this apporach too @mock.patch("tenacity.nap.time.sleep", MagicMock()) as that will auto-tear down after the test is finished, but monkey patching func.retry.sleep = mock.Mock() does not not tear down the monkey-patch after the test is complete.

richtier avatar Feb 24 '22 17:02 richtier