How to monkeypatch python's datetime.datetime.now with py.test?

Question:

I need to test functions which uses datetime.datetime.now(). What is the easiest way to do this?

Asked By: sashk

||

Answers:

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
Answered By: sashk

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
Answered By: Sebastian

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.

Answered By: jfs

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)
Answered By: Jake Levitt

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)

Answered By: Shinji Matsumoto

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
Answered By: yTek

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()
Answered By: Andrey Topoleov

Yet another recipe, using pytest-mock with an explicit MagicMock to wrap only the now method:

from datetime import datetime

from my_module import fn_returning_datetime_now

def test_datetime(mocker):
    NOW = dt.datetime(1999,1,1)
    mock_datetime = mocker.MagicMock(wraps=datetime)
    mock_datetime.now.return_value = NOW
    mocker.patch("my_module.datetime", mock_datetime)
    assert fn_returning_datetime_now == NOW

This is just a variation on other answers here, using mocker.patch instead of monkeypatch.

Answered By: 2e0byo

Let’s say that you want to mock datetime.now() in folder/subfolder/filname.py, which is the file were the function you want to test is in. Then:

The file to-be-tested:

# File path: folder/subfolder/filname.py
from datetime import datetime

def stringify_now():
     dt = datetime.now()
     return f"{dt}"

The test file:

import pytest
from datetime import datetime
from folder.subfolder.filename import stringify_now

def test_stringify_now(monkeypatch):
     frozen_curr_time = datetime(2022, 12, 2, 13, 0, 0)
     monkeypatch.setattr('folder.subfolder.filename.now', lambda: frozen_curr_time)
     actual = stringify_now()
     assert actual == '2022-12-02 13:00:00.000000'
Answered By: Ran Feldesh
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.