asqui: Ok, I think this branch is ready to merge now.
asqui: @ivanprado : Fascinating failure when I merged your change 78a462eb into this PR branch.

This PR makes a tweak to allow easier mocking of nap.sleep, but the test failures indicate that when using the `Retrying.call()` method (which is deprecated by your commit) `sleep()` is now getting called 24 times rather than 4.

I tweaked the test to invoke using `__call__` (i.e. `retrying(fn)` rather than `retrying.call(fn)` in the spirit of your change) and this resolves the issue, but it suggests that something is very wrong with the backward compatibility for `call()` introduced by your commit.

Smells like we're getting nested layers of retrying, but I can't immediately see how or why that is happening. (Also, if that were the case I'd intuitively expect 4 x 4 = 16 retries, but seems we're getting 24 (= 4 x 6?) retries.)

This failure also hints we might want to (re-)introduce test coverage for the `Retrying.call()` method.
ivanprado: Wow. I have no idea what can be going on. 

To give an idea, we might change this line https://github.com/jd/tenacity/commit/78a462eb71e13698fd0b8643b6c4336291ee013a#diff-03f4d209c0b66034a81979dd67efaa24R435 by:
```py
self(self, *args, **kwargs)
```

But in theory, it is completely equivalent. Could you try just in case it changes anything?
asqui: @ivanprado : The issue was that you are passing `self` as the first argument, which is meant to be the function to be executed by the retryer. That is, the retryer is going to execute _itself_, with retries!

PR #258 raised to resolve this.
jd: @Mergifyio update
mergify[bot]: **Command `update`: success**
> **Branch has been successfully updated**
> 

asqui: Tests are green again now.

Is there anything else required before this PR can be merged?
diff --git a/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml b/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
new file mode 100644
index 0000000..0a996e4
--- /dev/null
+++ b/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
@@ -0,0 +1,3 @@
+---
+other:
+  - Unit tests can now mock ``nap.sleep()`` for testing in all tenacity usage styles
\ No newline at end of file
diff --git a/tenacity/nap.py b/tenacity/nap.py
index e5bd8dc..83ff839 100644
--- a/tenacity/nap.py
+++ b/tenacity/nap.py
@@ -18,8 +18,14 @@
 
 import time
 
-#: Default sleep strategy.
-sleep = time.sleep
+
+def sleep(seconds):
+    """
+    Sleep strategy that delays execution for a given number of seconds.
+
+    This is the default strategy, and may be mocked out for unit testing.
+    """
+    time.sleep(seconds)
 
 
 class sleep_using_event(object):

Cannot easily mock out sleep function in order to simulate time in tests
Given the level of flexibility of this library, and a slightly elaborate context which I am using it in, it is desirable to be able to write an integration test where the concepts of 'time' (i.e. `time.monotonic`) and 'sleep' (i.e. `tenacity.nap.sleep`) are mocked out so that we can simulate in full fidelity how a retry scenario plays out, without having to actually sleep.

Using the [testfixtures](https://github.com/Simplistix/testfixtures) library and `mock.patch` I'm mocking out 'time' with an object that always returns the same 'virtual' time, and 'sleep' with a function that advances this 'virtual' time.

This approach works pretty well, save for one complication...

Since the signature of `BaseRetrying.__init__()` defaults the `sleep` argument to `tenacity.nap.sleep` this default is bound at import time, making it hard to mock out the sleep function after the fact.

Options I can think of:
1. Reach in to the method signature and mock out the default value - seems a bit messy.
2. Pass in a sleep function in the code under test, then mock this out in the test - I don't like this because it involves modifying production code simply for the sake of the test, and also makes this mocking strategy less portable if I want to use it in other tests (because now I need to mock out sleep at my callsite, rather than inside tenacity).
3. Make a change to the signature of `BaseRetrying.__init__()` to default `sleep` to `None`, and then default this to `nap.sleep` inside the method: `self.sleep = sleep or nap.sleep`.

Option 3 seems the neatest, because now I can just mock out `tenacity.nap.sleep` which is the most intuitive approach, but it does involve a change to the library which is why I wanted to solicit input first before preparing a PR.

Thoughts?




diff --git a/tenacity/tests/test_tenacity.py b/tenacity/tests/test_tenacity.py
index 673541c..223b19a 100644
--- a/tenacity/tests/test_tenacity.py
+++ b/tenacity/tests/test_tenacity.py
@@ -1565,5 +1565,50 @@ def reports_deprecation_warning():
         warnings.filters = oldfilters
 
 
+class TestMockingSleep():
+    RETRY_ARGS = dict(
+        wait=tenacity.wait_fixed(0.1),
+        stop=tenacity.stop_after_attempt(5),
+    )
+
+    def _fail(self):
+        raise NotImplementedError()
+
+    @retry(**RETRY_ARGS)
+    def _decorated_fail(self):
+        self._fail()
+
+    @pytest.fixture()
+    def mock_sleep(self, monkeypatch):
+        class MockSleep(object):
+            call_count = 0
+
+            def __call__(self, seconds):
+                self.call_count += 1
+
+        sleep = MockSleep()
+        monkeypatch.setattr(tenacity.nap.time, "sleep", sleep)
+        yield sleep
+
+    def test_call(self, mock_sleep):
+        retrying = Retrying(**self.RETRY_ARGS)
+        with pytest.raises(RetryError):
+            retrying.call(self._fail)
+        assert mock_sleep.call_count == 4
+
+    def test_decorated(self, mock_sleep):
+        with pytest.raises(RetryError):
+            self._decorated_fail()
+        assert mock_sleep.call_count == 4
+
+    def test_decorated_retry_with(self, mock_sleep):
+        fail_faster = self._decorated_fail.retry_with(
+            stop=tenacity.stop_after_attempt(2),
+        )
+        with pytest.raises(RetryError):
+            fail_faster()
+        assert mock_sleep.call_count == 1
+
+
 if __name__ == '__main__':
     unittest.main()

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest typeguard
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
exceptiongroup==1.2.2
importlib-metadata==6.7.0
iniconfig==2.0.0
packaging==24.0
pluggy==1.2.0
pytest==7.4.4
six==1.17.0
-e git+https://github.com/jd/tenacity.git@d66af82a70dd9af24a7953c6ae5f02d8f198d10f#egg=tenacity
tomli==2.0.1
typeguard==4.1.2
typing_extensions==4.7.1
zipp==3.15.0

name: tenacity
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - packaging==24.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - six==1.17.0
      - tomli==2.0.1
      - typeguard==4.1.2
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/tenacity

tenacity/tests/test_tenacity.py::TestMockingSleep::test_call tenacity/tests/test_tenacity.py::TestMockingSleep::test_decorated tenacity/tests/test_tenacity.py::TestMockingSleep::test_decorated_retry_with
tenacity/tests/test_tenacity.py::TestRetryTyping::test_retry_type_annotations
tenacity/tests/test_tenacity.py::TestBase::test_repr tenacity/tests/test_tenacity.py::TestStopConditions::test_legacy_explicit_stop_type tenacity/tests/test_tenacity.py::TestStopConditions::test_never_stop tenacity/tests/test_tenacity.py::TestStopConditions::test_retry_child_class_with_override_backward_compat tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_after_attempt tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_after_delay tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_all tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_and tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_any tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_backward_compat tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_func_with_retry_state tenacity/tests/test_tenacity.py::TestStopConditions::test_stop_or tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential_with_max_wait tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential_with_max_wait_and_multiplier tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential_with_min_wait tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential_with_min_wait_and_max_wait tenacity/tests/test_tenacity.py::TestWaitConditions::test_exponential_with_min_wait_and_multiplier tenacity/tests/test_tenacity.py::TestWaitConditions::test_fixed_sleep tenacity/tests/test_tenacity.py::TestWaitConditions::test_incrementing_sleep tenacity/tests/test_tenacity.py::TestWaitConditions::test_legacy_explicit_wait_type tenacity/tests/test_tenacity.py::TestWaitConditions::test_no_sleep tenacity/tests/test_tenacity.py::TestWaitConditions::test_random_sleep tenacity/tests/test_tenacity.py::TestWaitConditions::test_random_sleep_without_min tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_arbitrary_sum tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_backward_compat tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_backward_compat_with_result tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_chain tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_chain_multiple_invocations tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_class_backward_compatibility tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_combine tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_double_sum tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_func tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_random_exponential tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_random_exponential_statistically tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_retry_state_attributes tenacity/tests/test_tenacity.py::TestWaitConditions::test_wait_triple_sum tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_all tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_and tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_any tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_if_exception_message_negative_no_inputs tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_if_exception_message_negative_too_many_inputs tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_if_not_result tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_if_result tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_or tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_try_again tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_try_again_forever tenacity/tests/test_tenacity.py::TestRetryConditions::test_retry_try_again_forever_reraise tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_defaults tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_child_class_with_override_backward_compat tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_function_object tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_exception_message tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_exception_message_match tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_exception_of_type tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_not_exception_message tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_not_exception_message_delay tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_if_not_exception_message_match tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_until_exception_of_type_attempt_number tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_until_exception_of_type_no_type tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_until_exception_of_type_wrong_exception tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_retry_with tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_with_stop_on_exception tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_with_stop_on_return_value tenacity/tests/test_tenacity.py::TestDecoratorWrapper::test_with_wait tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_after_attempts tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_attempts tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_backward_compat tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_backward_compat_method tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_log_raises tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_log_raises_deprecated_call tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_log_raises_with_exc_info tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_log_returns tenacity/tests/test_tenacity.py::TestBeforeAfterAttempts::test_before_sleep_log_returns_with_exc_info tenacity/tests/test_tenacity.py::TestReraiseExceptions::test_reraise_by_default tenacity/tests/test_tenacity.py::TestReraiseExceptions::test_reraise_from_retry_error tenacity/tests/test_tenacity.py::TestReraiseExceptions::test_reraise_no_exception tenacity/tests/test_tenacity.py::TestReraiseExceptions::test_reraise_timeout_from_retry_error tenacity/tests/test_tenacity.py::TestStatistics::test_stats tenacity/tests/test_tenacity.py::TestStatistics::test_stats_failing tenacity/tests/test_tenacity.py::TestRetryErrorCallback::test_retry_error_callback tenacity/tests/test_tenacity.py::TestRetryErrorCallback::test_retry_error_callback_backward_compat tenacity/tests/test_tenacity.py::TestContextManager::test_context_manager_on_error tenacity/tests/test_tenacity.py::TestContextManager::test_context_manager_reraise tenacity/tests/test_tenacity.py::TestContextManager::test_context_manager_retry_error tenacity/tests/test_tenacity.py::TestContextManager::test_context_manager_retry_one tenacity/tests/test_tenacity.py::TestInvokeAsCallable::test_retry_one tenacity/tests/test_tenacity.py::TestInvokeAsCallable::test_on_error tenacity/tests/test_tenacity.py::TestInvokeAsCallable::test_retry_error tenacity/tests/test_tenacity.py::TestInvokeAsCallable::test_reraise tenacity/tests/test_tenacity.py::TestInvokeViaLegacyCallMethod::test_retry_one tenacity/tests/test_tenacity.py::TestInvokeViaLegacyCallMethod::test_on_error tenacity/tests/test_tenacity.py::TestInvokeViaLegacyCallMethod::test_retry_error tenacity/tests/test_tenacity.py::TestInvokeViaLegacyCallMethod::test_reraise tenacity/tests/test_tenacity.py::TestRetryException::test_retry_error_is_pickleable
tenacity/nap.py releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
tenacity/nap.py releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml