--- myst: html_meta: description: >- Testing code that uses whenever: patch_current_time and its TimePatch handle, the time-machine package, dependency injection instead of patching, and setting the system time zone via TZ. --- # 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: 1. With {func}`whenever.patch_current_time`. It affects only `whenever`, not the standard library or other libraries. See its documentation for more details. 2. With the [`time-machine`](https://github.com/adamchainz/time-machine) package. Using `time-machine` *does* affect the standard library and other libraries, which can lead to unintended side effects. Note that `time-machine` doesn't support PyPy. ```{note} [freezegun](https://github.com/spulec/freezegun) isn't supported. Use `time-machine` instead. ``` The context manager yields a {class}`~whenever.TimePatch` handle to the **time patch**. Use {meth}`~whenever.TimePatch.shift` for exact elapsed-time movement and {meth}`~whenever.TimePatch.move_to` to set a new exact time: ```python >>> 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 {exc}`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: ```python 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 {func}`~whenever.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`: ```python 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! ```