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

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

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


当前回答

对于那些在测试类中使用补丁程序的人,下面是我如何成功地修补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'
        )

其他回答

在Daniel G的解决方案中补充一点:

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

这将创建一个类,当实例化时,该类将返回一个正常的datetime。对象,但也可以更改。

@mock.patch('datetime.date', FakeDate)
def test():
    from datetime import date
    FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
    return date.today()

test() # datetime.date(2010, 1, 1)

也许您可以使用自己的“today()”方法,在需要的地方打补丁。使用utcnow()的示例可以在这里找到:https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default

你可以用这个来模拟datetime:

在sources.py模块中:

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

在您的tests.py中:

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')

monkeypatch的最小工作示例

这个解决方案使用https://pypi.org/project/pytest-mock/包中的monkeypatch。

特点:

仅模拟datetime.today(),但datetime.now()仍然可以正常工作 仅在特定范围内模拟(即块)

import sys
from datetime import datetime

MOCKED_DATETIME_TODAY = datetime(1900, 1, 1, 0, 0, 0)

class MockedDatetime(datetime):
    @classmethod
    def today(cls):
        return MOCKED_DATETIME_TODAY

def test_mock_datetime_today(monkeypatch):
    """Only datetime.today() is mocked and returns some date in 1900. datetime.now() returns still the current date."""
    with monkeypatch.context() as mpc:
        mpc.setattr(sys.modules[__name__], 'datetime', MockedDatetime)
        assert datetime.today() == MOCKED_DATETIME_TODAY  # datetime.today() mocked
        assert datetime.now() > MOCKED_DATETIME_TODAY    # datetime.now() not mocked

    assert datetime.today() > MOCKED_DATETIME_TODAY  # not mocked anymore

可以在不添加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()