'How to monkeypatch python's datetime.datetime.now with py.test?
I need to test functions which uses datetime.datetime.now()
. What is the easiest way to do this?
Solution 1:[1]
You need to monkeypatch datetime.now function. In example below, I'm creating fixture which I can re-use later in other tests:
import datetime
import pytest
FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 5, 55)
@pytest.fixture
def patch_datetime_now(monkeypatch):
class mydatetime:
@classmethod
def now(cls):
return FAKE_TIME
monkeypatch.setattr(datetime, 'datetime', mydatetime)
def test_patch_datetime(patch_datetime_now):
assert datetime.datetime.now() == FAKE_TIME
Solution 2:[2]
There is freezegun
module:
from datetime import datetime
from freezegun import freeze_time # $ pip install freezegun
@freeze_time("Jan 14th, 2012")
def test_nice_datetime():
assert datetime.now() == datetime(2012, 1, 14)
freeze_time()
could also be used as a context manager. The module support specifying the local timezone UTC offset.
Solution 3:[3]
This is the fixture I use for overriding now() but keeping the rest of datetime working (RE: satoru's question).
It is not extensively tested, but it does get around issues where datetime is used in other contexts. For me this was important to keep the Django ORM working with these datetime values (Specifically isinstance(Freeze.now(), datetime.datetime) == True
).
@pytest.fixture
def freeze(monkeypatch):
""" Now() manager patches datetime return a fixed, settable, value
(freezes time)
"""
import datetime
original = datetime.datetime
class FreezeMeta(type):
def __instancecheck__(self, instance):
if type(instance) == original or type(instance) == Freeze:
return True
class Freeze(datetime.datetime):
__metaclass__ = FreezeMeta
@classmethod
def freeze(cls, val):
cls.frozen = val
@classmethod
def now(cls):
return cls.frozen
@classmethod
def delta(cls, timedelta=None, **kwargs):
""" Moves time fwd/bwd by the delta"""
from datetime import timedelta as td
if not timedelta:
timedelta = td(**kwargs)
cls.frozen += timedelta
monkeypatch.setattr(datetime, 'datetime', Freeze)
Freeze.freeze(original.now())
return Freeze
Perhaps off topic, but might come in handy to other people arriving at this question. This fixture allows "freezing" time, and then moving it back and forth at will within your tests:
def test_timesensitive(freeze):
freeze.freeze(2015, 1, 1)
foo.prepare() # Uses datetime.now() to prepare its state
freeze.delta(days=2)
# Does something that takes in consideration that 2 days have passed
# i.e. datetime.now() returns a date 2 days in the future
foo.do_something()
assert foo.result == expected_result_after_2_days
Solution 4:[4]
How about using MagicMock(wrap=datetime.datetime)
?
This aproach mocks datetime.datetime.now()
but the other methods are available same with the original datetime.datetime
.
from unittest.mock import MagicMock
def test_datetime_now(monkeypatch):
import datetime
FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)
datetime_mock = MagicMock(wraps=datetime.datetime)
datetime_mock.now.return_value = FAKE_NOW
monkeypatch.setattr(datetime, "datetime", datetime_mock)
assert datetime.datetime.now() == FAKE_NOW
# the other methods are available
assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)
Using @pytest.fixture
approach is here.
import datetime
from unittest.mock import MagicMock
import pytest
FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)
@pytest.fixture()
def mock_datetime_now(monkeypatch):
datetime_mock = MagicMock(wraps=datetime.datetime)
datetime_mock.now.return_value = FAKE_NOW
monkeypatch.setattr(datetime, "datetime", datetime_mock)
def test_datetime_now2(mock_datetime_now):
assert datetime.datetime.now() == FAKE_NOW
assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)
Solution 5:[5]
Adapted from the other answers:
import datetime as dt
from contextlib import contextmanager
from unittest.mock import patch
@contextmanager
def mocked_now(now):
class MockedDatetime(dt.datetime):
@classmethod
def now(cls):
return now
with patch("datetime.datetime", MockedDatetime):
yield
Used like:
def test_now():
with mocked_now(dt.datetime(2017, 10, 21)):
assert dt.datetime.now() == dt.datetime(2017, 10, 21)
Solution 6:[6]
it handly to use unittest.mock.patch as context manager, wright in test:
import datetime
import pytest
from unittest.mock import patch
def test_schedule(schedules_db_client):
faked_now = datetime.datetime(2022, 1, 1, 14, 0, 0)
with patch("datetime.datetime") as mock_datetime:
mock_datetime.now.return_value = faked_now
assert schedules_db_client.get_current_attendant()
Solution 7:[7]
If you are a lover of pytest-mock, then mock the datetime.now
using this.
from datetime import datetime, timezone
def test_time_now(mocker):
#patch the the datetime method in your file
mock_date = mocker.patch("your_package.your_package.datetime")
FAKE_NOW = datetime(2020, 3, 11, 14, 0, 0, tzinfo=timezone.utc)
mock_date.now.return_value = FAKE_NOW
from your_package.your_package import time_to_test
timestamp_method = time_to_test()
assert timestamp_method == FAKE_NOW
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | jfs |
Solution 3 | Sebastian |
Solution 4 | |
Solution 5 | Fred |
Solution 6 | Andrey Topoleov |
Solution 7 | yTek |