65

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

reidhoch
  • 39
  • 5
sashk
  • 3,946
  • 2
  • 33
  • 41

9 Answers9

73

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
sashk
  • 3,946
  • 2
  • 33
  • 41
  • 11
    Is it possible to only replace the `now` method? – satoru Sep 26 '14 at 03:14
  • 11
    This works, but not if you do "from datetime import datetime". Take a look here for detais: http://stackoverflow.com/questions/35431476/why-pythons-monkeypatch-doesnt-work-when-importing-a-class-instead-of-a-module – rgargente Feb 16 '16 at 11:59
  • 1
    I was able to adjust this answer and patch `utcnow` – Dror Jan 15 '20 at 13:09
  • 4
    Your `mydatetime` class should subclass `datetime.datetime`, otherwise it won't be able to perform any of the other methods datetime.datetime normally performs. – mareoraft Aug 31 '21 at 16:23
  • @mareoraft That might not be enough. I tried that, and it works well enough in the MCVE, but in my real environment a pandas import failed with `TypeError: type 'pandas._libs.tslibs.base.ABCTimestamp' is not dynamically allocated but its base type 'mydatetime' is dynamically allocated`. – gerrit Sep 30 '22 at 15:37
55

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.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
14

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)

Shinji Matsumoto
  • 163
  • 2
  • 10
10

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
Sebastian
  • 2,678
  • 25
  • 24
6

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
yTek
  • 59
  • 1
  • 3
3

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()
Andrey Topoleov
  • 1,591
  • 15
  • 20
2

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)
Fred
  • 1,103
  • 2
  • 14
  • 35
Jake Levitt
  • 120
  • 1
  • 9
1

#mymodule.py

import datetime

#test file

@patch('mymodule.datetime')
def test_stomething(datetime_mock):
                                                                                
    frozen_now = datetime.datetime(2000, 1, 1)
    # I here I mock the now call
    datetime_mock.datetime.now.return_value = frozen_now
    # here I want timedelta to actually call the original function
    datetime_mock.timedelta = datetime.timedelta
jeckyll86
  • 21
  • 6
0

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.

2e0byo
  • 5,305
  • 1
  • 6
  • 26