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

PythonDatetimeMockingPytest

Python Problem Overview


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

Python Solutions


Solution 1 - Python

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 - Python

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 - Python

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 - Python

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 - Python

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 - Python

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

Solution 7 - Python

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsashkView Question on Stackoverflow
Solution 1 - PythonsashkView Answer on Stackoverflow
Solution 2 - PythonjfsView Answer on Stackoverflow
Solution 3 - PythonSebastianView Answer on Stackoverflow
Solution 4 - PythonShinji MatsumotoView Answer on Stackoverflow
Solution 5 - PythonJake LevittView Answer on Stackoverflow
Solution 6 - PythonyTekView Answer on Stackoverflow
Solution 7 - PythonAndrey TopoleovView Answer on Stackoverflow