Testing¶
Patching the current time¶
Sometimes you need to control what .now() returns, typically for testing.
whenever supports various ways to do this, depending on your needs:
With
whenever.patch_current_time(). It affects onlywhenever, not the standard library or other libraries. See its documentation for more details.With the
time-machinepackage. Usingtime-machinedoes affect the standard library and other libraries, which can lead to unintended side effects. Note thattime-machinedoesn’t support PyPy.
Note
freezegun isn’t supported.
Use time-machine instead.
The context manager yields a TimePatch handle to the
time patch. Use shift() for exact elapsed-time
movement and move_to() to set a new exact time:
>>> from whenever import Instant, patch_current_time
>>> with patch_current_time(Instant("2024-01-01T00:00:00Z"), keep_ticking=False) as p:
... p.shift(hours=2)
... assert Instant.now() == Instant("2024-01-01T02:00:00Z")
... p.move_to(Instant("2024-06-01T12:00:00Z"))
... assert Instant.now() == Instant("2024-06-01T12:00:00Z")
shift() moves by elapsed time; days= and weeks= count 24-hour days and
warn, as Instant.add() does. For months or years, compute the target and
pass it to move_to(). A time patch is either
frozen (keep_ticking=False) or ticking (keep_ticking=True). With a
ticking patch, shifts apply to the patched current instant at the moment of
the call and the clock then continues ticking from the result.
The patch affects only Whenever’s current-time functions. Its state is global
to the interpreter/module and therefore visible to every thread. Overlapping
or nested patches raise RuntimeError, as does using a handle after its
context exits. Decorator use does not inject the handle into the decorated
function; use a with statement when you need it. In free-threaded builds,
Whenever synchronizes access to this global state.
Tip
Instead of relying on patching, consider using dependency injection instead. This is less error-prone and more explicit.
You can do this by adding now argument to your function,
like this:
def greet(name, now=Instant.now):
current_time = now()
# more code here...
# in normal use, you don't notice the difference:
greet('bob')
# to test it, pass a custom function:
greet('alice', now=lambda: Instant.from_utc(2023, 1, 1))
Patching the system time zone¶
For changing the system time zone in tests, set the TZ environment variable
and use the reset_system_tz() helper function to update the time zone cache.
Do note that this function only affects whenever, and not the standard library’s
behavior.
Below is an example of a testing helper that can be used with pytest:
import os
import pytest
from contextlib import contextmanager
from unittest.mock import patch
from whenever import reset_system_tz
@contextmanager
def system_tz_ams():
try:
with patch.dict(os.environ, {"TZ": "Europe/Amsterdam"}):
reset_system_tz() # update the time zone cache
yield
finally:
reset_system_tz() # don't forget to set the old time zone back!