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:
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:
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:
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:
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)