谁能告诉我为什么这行不通?

>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
...  return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)

也许有人能提出一个更好的办法?


当前回答

CPython实际上使用纯python Lib/datetime.py和c优化的Modules/ _datetimmodule .c实现了datetime模块。c优化版本不能打补丁,但纯python版本可以。

在Lib/datetime.py中纯python实现的底部是这样的代码:

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass

这段代码导入了所有c优化的定义,并有效地替换了所有纯python定义。我们可以通过以下方式强制CPython使用datetime模块的纯python实现:

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)

通过设置sys。modules["_datetime"] = None,我们告诉Python忽略c优化的模块。然后重新加载导致从_datetime导入失败的模块。现在纯python定义仍然存在,可以正常打补丁。

如果你正在使用Pytest,那么在conftest.py中包含上面的代码片段,你就可以正常地修补datetime对象了。

其他回答

http://blog.xelnor.net/python-mocking-datetime/中讨论了几种解决方案。总而言之:

模拟对象-简单有效,但打破isinstance()检查:

target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
    patched.now.return_value = target
    print(datetime.datetime.now())

模拟类

import datetime
import mock

real_datetime_class = datetime.datetime

def mock_datetime_now(target, dt):
    class DatetimeSubclassMeta(type):
        @classmethod
        def __instancecheck__(mcs, obj):
            return isinstance(obj, real_datetime_class)

    class BaseMockedDatetime(real_datetime_class):
        @classmethod
        def now(cls, tz=None):
            return target.replace(tzinfo=tz)

        @classmethod
        def utcnow(cls):
            return target

    # Python2 & Python3 compatible metaclass
    MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})

    return mock.patch.object(dt, 'datetime', MockedDatetime)

使用:

with mock_datetime_now(target, datetime):
   ....

我通过将datetime导入为realdatetime并将我在模拟中需要的方法替换为实际方法来完成这项工作:

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)

可以在不添加side_effects的情况下模拟datetime模块中的函数

import mock
from datetime import datetime
from where_datetime_used import do

initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
    mocked_dt.now.return_value = initial_date
    do()

值得注意的是,Mock文档专门讨论了datetime.date.today,并且可以在不创建虚拟类的情况下做到这一点:

https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...

对于那些使用pytest和pytest-mock的人(关于pytest-mock的更多信息在最后),这里是我如何模拟datetime.datetime.now(),这与最初的问题非常相似。

test_get_now(mocker):
    datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
    datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)
    
    now == function_being_tested()  # run function

    assert now == datetime.datetime(2019,3,11,6,2,0,0)

本质上,mock必须设置为返回指定的日期。您不能直接修补datetime的对象。

Pytest-mock是一个使模拟对象成为fixture的库。更多细节可以在这里找到