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

Viewed 23950

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

8 Answers

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)

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)

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

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

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.

Related