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

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

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


当前回答

有一些问题。

首先,你使用模拟的方式。帕奇说得不太对。当用作装饰器时,它只在被装饰的函数内用Mock对象替换给定的函数/类(在本例中为datetime.date.today)。因此,只有在today()中,datetime.date.today才会是一个不同的函数,这似乎不是你想要的。

你真正想要的似乎是这样的:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()

不幸的是,这行不通:

>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'

这是失败的,因为Python内置类型是不可变的-更多细节请参阅这个答案。

在本例中,我将继承datetime的子类。日期自己,并创建正确的函数:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate

现在你可以这样做:

>>> datetime.date.today()
NewDate(2010, 1, 1)

其他回答

我通过将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)

对我来说最简单的方法是:

import datetime
from unittest.mock import Mock, patch

def test():
    datetime_mock = Mock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
    with patch('datetime.datetime', new=datetime_mock):
        assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)

注意:target_module中的datetime模块中的所有功能都将停止工作。

对于那些在测试类中使用补丁程序的人,下面是我如何成功地修补datetime功能:

from datetime import datetime
import unittest
from unittest.mock import Mock, patch

# Replace with the proper path to the module you would
# like datetime to be mocked
from path.to.my_module

class MyTestCases(unittest.TestCase):

    def setUp(self):
        """execute on class instantiation"""
        # Record both times at the same moment
        self.dt_now, self.dt_utcnow = datetime.now(), datetime.utcnow()

        # After retrieving real (or hardcoded datetime values), 
        # proceed to mock them in desired module
        self.patch_datetime_functions()


    def patch_datetime_functions(self) -> None:
        """
        Patch datetime.now() and datetime.utcnow() to prevent issues when
        comparing expected dates
        """

        # Create a patcher
        self.patcher_dt = patch(
            'path.to.my_module'
        )

        # Start but make sure cleanup always occurs
        self.patcher_dt.start()
        self.addCleanup(self.patcher_dt.stop)

        # Perform the actual patch – use lambdas as mock functions
        datetime_mock = Mock(wraps=datetime)
        datetime_mock.now.return_value = self.dt_now
        datetime_mock.utcnow.return_value = self.dt_utcnow

        my_module.datetime = datetime_mock


    # Here's what it will look like when testing:
    def some_test(self):
        curr_dt = self.dt_now
        returned_dt = my_module.datetime.utcnow()
        
        # Compare the dates
        self.assertEqual(curr_dt, returned_dt,
            'Datetime values should be equal'
        )

我们可以使用pytest-mock (https://pypi.org/project/pytest-mock/)模拟器对象来模拟特定模块中的日期时间行为

假设您想在以下文件中模拟日期时间

# File path - source_dir/x/a.py
import datetime

def name_function():
     name = datetime.now()
     return f"name_{name}"

在测试函数中,mock将在测试运行时添加到函数中

def test_name_function(mocker):
     mocker.patch('x.a.datetime')
     x.a.datetime.now.return_value = datetime(2019, 1, 1)

     actual = name_function()

     assert actual == "name_2019-01-01"

您可以使用以下基于Daniel G解的方法。这个方法的优点是不会破坏isinstance(d, datetime.date)的类型检查。

import mock

def fixed_today(today):
    from datetime import date

    class FakeDateType(type):
        def __instancecheck__(self, instance):
            return isinstance(instance, date)

    class FakeDate(date):
        __metaclass__ = FakeDateType

        def __new__(cls, *args, **kwargs):
            return date.__new__(date, *args, **kwargs)

        @staticmethod
        def today():
            return today

    return mock.patch("datetime.date", FakeDate)

基本上,我们取代了基于c语言的datetime。Date类,使用我们自己的python子类,生成原始的datetime。日期实例,并响应isinstance()查询完全与本机datetime.date相同。

在测试中使用它作为上下文管理器:

with fixed_today(datetime.date(2013, 11, 22)):
    # run the code under test
    # note, that these type checks will not break when patch is active:
    assert isinstance(datetime.date.today(), datetime.date)

类似的方法可用于模拟datetime.datetime.now()函数。