Skip to content

Commit 4178b1d

Browse files
kingbuzzmanadamchainz
authored andcommitted
Add marker support to pytest plugin
1 parent 428491e commit 4178b1d

File tree

5 files changed

+69
-3
lines changed

5 files changed

+69
-3
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Changelog
1010
2.18.0 (2025-08-18)
1111
-------------------
1212

13+
* Add a pytest marker fixture supporting initial time setting and in-test time shifting.
14+
15+
Thanks to Javier Buzzi in `PR #499 <https://github.com/adamchainz/time-machine/pull/499>`__.
16+
1317
* Update the :ref:`migration CLI <migration-cli>` to detect unittest classes based on whether they use ``self.assert*`` methods like ``self.assertEqual()``.
1418

1519
* Fix free-threaded Python warning: ``RuntimeWarning: The global interpreter lock (GIL) has been enabled...`` as seen on Python 3.13+.

docs/pytest_plugin.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pytest plugin
33
=============
44

55
time-machine also works as a pytest plugin.
6-
It provides a function-scoped fixture called ``time_machine`` with methods ``move_to()`` and ``shift()``, which have the same signature as their equivalents in ``Coordinates``.
6+
It provides a marker and function-scoped fixture called ``time_machine`` with methods ``move_to()`` and ``shift()``, which have the same signature as their equivalents in ``Coordinates``.
77
This can be used to mock your test at different points in time and will automatically be un-mock when the test is torn down.
88

99
For example:
@@ -13,6 +13,13 @@ For example:
1313
import datetime as dt
1414
1515
16+
@pytest.mark.time_machine(dt.datetime(1985, 10, 26))
17+
def test_delorean_marker(time_machine):
18+
assert dt.date.today().isoformat() == "1985-10-26"
19+
time_machine.move_to(dt.datetime(2015, 10, 21))
20+
assert dt.date.today().isoformat() == "2015-10-21"
21+
22+
1623
def test_delorean(time_machine):
1724
time_machine.move_to(dt.datetime(1985, 10, 26))
1825

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ max_supported_python = "3.14"
130130
addopts = """\
131131
--strict-config
132132
--strict-markers
133+
-p pytester
133134
"""
134135
xfail_strict = true
135136

src/time_machine/__init__.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,24 @@ def time_ns() -> int:
378378
# pytest plugin
379379

380380
if HAVE_PYTEST: # pragma: no branch
381+
MARKER_NAME = "time_machine"
382+
FIXTURE_NAME = "time_machine"
383+
384+
def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
385+
"""
386+
Inject our fixture into any tests with our marker
387+
"""
388+
for item in items:
389+
if item.get_closest_marker(MARKER_NAME):
390+
item.fixturenames.insert(0, FIXTURE_NAME) # type: ignore[attr-defined]
391+
392+
def pytest_configure(config: pytest.Config) -> None:
393+
"""
394+
Register our marker
395+
"""
396+
config.addinivalue_line(
397+
"markers", f"{MARKER_NAME}(...): use time machine to set time"
398+
)
381399

382400
class TimeMachineFixture:
383401
traveller: travel | None
@@ -413,9 +431,15 @@ def stop(self) -> None:
413431
if self.traveller is not None:
414432
self.traveller.stop()
415433

416-
@pytest.fixture(name="time_machine")
417-
def time_machine_fixture() -> TypingGenerator[TimeMachineFixture, None, None]:
434+
@pytest.fixture(name=FIXTURE_NAME)
435+
def time_machine_fixture(
436+
request: pytest.FixtureRequest,
437+
) -> TypingGenerator[TimeMachineFixture, None, None]:
418438
fixture = TimeMachineFixture()
439+
marker = request.node.get_closest_marker(MARKER_NAME)
440+
if marker:
441+
fixture.move_to(*marker.args, **marker.kwargs)
442+
419443
yield fixture
420444
fixture.stop()
421445

tests/test_time_machine.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,36 @@ def test_fixture_shift_without_move_to(time_machine):
948948
)
949949

950950

951+
def test_standalone_marker(testdir):
952+
testdir.makepyfile(
953+
"""
954+
import pytest
955+
import time
956+
957+
@pytest.fixture
958+
def current_time():
959+
return time.time()
960+
961+
@pytest.fixture
962+
def set_time(time_machine):
963+
time_machine.move_to("2000-01-01")
964+
965+
def test_normal(current_time):
966+
assert current_time > 1742943111.0
967+
968+
@pytest.mark.time_machine("2000-01-01")
969+
def test_mod(current_time, time_machine):
970+
assert current_time == 946684800.0
971+
time_machine.shift(1)
972+
assert current_time == 946684800.0
973+
assert int(time.time()) == 946684801
974+
"""
975+
)
976+
977+
result = testdir.runpytest("-v", "-s")
978+
assert result.ret == 0
979+
980+
951981
# escape hatch tests
952982

953983

0 commit comments

Comments
 (0)