Search code examples
pythonmockingpython-mock

How to mock the "+" operator in python (specifically datetime.date + datetime.timedelta)


I have working through some date mocking issues in Django, and have the final hurdle (I hope) is the following situation. I have a FakeDate class, which derives from datetime.date, which it mocks out.

The FakeDate class works as expected, however I get a problem when adding a datetime.timedelta to the FakeDate, in that it returns a genuine datetime.date, rather than the mock. This is important as elsewhere in a third party library there is an isinstance(value, datetime.date) check, which will always fail when using timedelta.

>>> import mock
>>> import datetime
>>>
>>> class FakeDate(datetime.date):
...     @classmethod
...     def today(cls):
...         return cls(1999, 12, 31)
...
>>> FakeDate.today()
FakeDate(1999, 12, 31)
>>> FakeDate(2000, 1, 1)
FakeDate(2000, 1, 1)
>>> FakeDate(1999, 12, 31) + datetime.timedelta(days=1)
datetime.date(2000, 1, 1)

I want the FakeDate + timedelta addition to return a FakeDate object rather than a datetime.date object - which I imagine involves patching the timedelta somehow - but how / where can I do this?


Solution

  • Add a __add__ method to your FakeDate() class:

    class FakeDate(datetime.date):
         @classmethod
         def today(cls):
             return cls(1999, 12, 31)
         def __add__(self, other):
             res = super(FakeDate, self).__add__(other)
             return type(self)(res.year, res.month, res.day)
    

    Demo:

    >>> class FakeDate(datetime.date):
    ...      @classmethod
    ...      def today(cls):
    ...          return cls(1999, 12, 31)
    ...      def __add__(self, other):
    ...          res = super(FakeDate, self).__add__(other)
    ...          return type(self)(res.year, res.month, res.day)
    ... 
    >>> FakeDate.today() + datetime.timedelta(days=1)
    FakeDate(2000, 1, 1)
    

    Note that you can simply delegate the actual adding to the datetime.date class here; all we need to do is convert the result back to a FakeDate() instance.