如何用py.test monkeypatch python的datetime.datetime.now?

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

我需要测试使用datetime.datetime.now()的函数。 最简单的方法是什么?


你需要monkeypatch datetime.now函数。 在下面的例子中,我正在创建夹具,我可以在以后的其他测试中重复使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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


freezegun模块:

1
2
3
4
5
6
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()也可以用作上下文管理器。 该模块支持指定本地时区UTC偏移量。


这是我用来覆盖now()的工具,但保持datetime的其余部分工作(RE:satoru的问题)。

它没有经过广泛测试,但确实解决了在其他环境中使用datetime的问题。 对我来说,保持Django ORM使用这些日期时间值(特别是isinstance(Freeze.now(), datetime.datetime) == True)非常重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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

也许是偏离主题,但可能会派到其他人来处理这个问题。 这个夹具允许"冻结"时间,然后在你的测试中随意移动它:

1
2
3
4
5
6
7
8
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

改编自其他答案:

1
2
3
4
5
6
7
8
9
10
11
import datetime as dt

@contextmanager
def mocked_now(now):
    class MockedDatetime(dt.datetime):
        @classmethod
        def now(cls):
            return now

    with patch("datetime.datetime", MockedDatetime):
        yield

使用如下:

1
2
3
def test_now():
    with mocked_now(dt.datetime(2017, 10, 21)):
        assert dt.datetime.now() == dt.datetime(2017, 10, 21)