Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable wait for unittest #106

Open
DanEEStar opened this issue Dec 20, 2017 · 14 comments
Open

Disable wait for unittest #106

DanEEStar opened this issue Dec 20, 2017 · 14 comments

Comments

@DanEEStar
Copy link

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
Copy link
Owner

jd commented Dec 20, 2017

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
Copy link
Contributor

immerrr commented Jul 9, 2018

@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
Copy link

steveb commented Aug 28, 2018

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
Copy link

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

@dvcolgan
Copy link

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:

openstack-archive/tripleo-common@eba4912#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
Copy link

@davidscolgan Thank you

@dtantsur
Copy link

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
Copy link

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
Copy link

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.

@robfraz
Copy link

robfraz commented Jul 2, 2024

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
Copy link

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
Copy link

ikbenale commented Aug 6, 2024

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

@jmehnle
Copy link

jmehnle commented Aug 14, 2024

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
Copy link

mastizada commented Oct 13, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests