jd / tenacity

Retrying library for Python
http://tenacity.readthedocs.io
Apache License 2.0
6.75k stars 281 forks source link

Disable `wait` for unittest #106

Open DanEEStar opened 6 years ago

DanEEStar commented 6 years ago

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

jd commented 6 years ago

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.

immerrr commented 6 years ago

@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.

steveb commented 6 years ago

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

josephbosire commented 6 years ago

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

dvcolgan commented 5 years ago

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

Gatsby-Lee commented 5 years ago

@davidscolgan Thank you

dtantsur commented 4 years ago

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.

richtier commented 2 years ago

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.

skinitimski commented 4 months ago

You can change the wait function temporarily in your test:

func.retry.wait = wait_none

This approach no longer works after https://github.com/jd/tenacity/pull/479. I am instead trying to use the solution enabled by https://github.com/jd/tenacity/pull/236 but not having much luck yet.

robfraz commented 4 months ago

You can change the wait function temporarily in your test:

func.retry.wait = wait_none

This approach no longer works after #479. I am instead trying to use the solution enabled by #236 but not having much luck yet.

Yes, I'm also hitting this problem.

thomas-dufour commented 4 months ago

I was hitting this problem too but thanks to (https://stackoverflow.com/a/73890688) I managed to avoid this problem by patching tenacity nap directly. mock.patch("tenacity.nap.time")

ikbenale commented 3 months ago

In my case since my function was async I had to @mock.patch("asyncio.sleep")

jmehnle commented 3 months ago

My application talks to a variety of other services through (async) client libraries, and they each use a dedicated Retrying (or AsyncRetrying) object, so I need a way to globally disable sleep during unit tests rather than patching each individual Retrying (or AsyncRetrying) object. Alas, currently there is no such way. In particular, monkey-patching asyncio.sleep (and, in my app's case, trio.sleep) is not acceptable, because this function serves other important purposes.

What I ended up doing was to:

  1. define a _sleep function that's passed to the sleep argument of all my (Async)Retrying instances.
  2. have this _sleep function check a global _sleep_enabled variable (statically set to True), and call the actual sleep (in my case tenacity.asyncio._portable_async_sleep) only if that's True.
  3. define a no_retry_sleep context manager that temporarily sets the _sleep_enabled global to False, yields, and finally restores it to True.

See https://gist.github.com/jmehnle/2cf4deba48c32c01fdf6c0ee5248a1d1.

Now I can use that no_retry_sleep context manager in my pytest tests or fixtures to disable sleep.

I think adding a native facility to tenacity for disabling sleep could be useful, but that's also possibly an incomplete solution as when you use retry with settings like stop=stop_after_delay(600), then unit tests that actually exercise retry may still take 10 minutes to complete. So you'd also have to override the stop setting to something like stop_after_attempt(2). So making tenacity behave well during unit tests is nontrivial and strongly depends on your actual retry configuration.

mastizada commented 1 month ago

I used this in my conftest to disable waiting for all my retry cases:

tenacity.wait.wait_exponential_jitter.__call__ = lambda __, ___: 0

I want the retry to happen because I want to make sure that my function does retry on certain cases.