pythondatemockingpytestpython-unittest

Mock date.today() but leave other date methods alone


I am trying to test some python code that involves setting/comparing dates, and so I am trying to leverage unittest.mock in my testing (using pytest). The current problem I'm hitting is that using patch appears to override all the other methods for the patched class (datetime.date) and so causes other errors because my code is using other methods of the class.

Here is a simplified version of my code.

#main.py
from datetime import date, timedelta, datetime

def date_distance_from_today(dt: str | date) -> timedelta:
    if not isinstance(dt, date):
        dt = datetime.strptime(dt, "%Y-%m-%d").date()
    return date.today() - dt
#tests.py
from datetime import date, timedelta
from unittest.mock import patch

from mock_experiment import main

def test_normal(): # passes fine today, Jan 7
    assert main.date_distance_from_today(date(2025, 1, 1)) == timedelta(6)

def test_normal_2(): # passes fine today, Jan 7
    assert main.date_distance_from_today("2025-01-01") == timedelta(6)

def test_with_patch_on_date(): # exception thrown
    with patch("mock_experiment.main.date") as patch_date:
        patch_date.today.return_value = date(2025, 1, 2)
        assert main.date_distance_from_today(date(2025, 1, 1)) == timedelta(1)

When I run these tests, the first two pass but the third gets the following exception:

def func1(dt: str | date) -> timedelta:
>       if not isinstance(dt, date):
E       TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union

This makes sense to me (although not what I want) since I borked out the date object and turned it into a MagicMock and so it doesn't get handled how I want in this isinstance call.

I also tried patching date.today, which also failed as shown below:

def test_with_mock_on_today():
    with patch("mock_experiment.main.date.today") as patch_today:
        patch_today.return_value = date(2025, 1, 2)
        assert main.distance_from_today(date(2025, 1, 1)) == timedelta(1)

Exception

TypeError: cannot set 'today' attribute of immutable type 'datetime.date'

Solution

  • Description of changes to the file main.py

    I have found a possible solution by the modification of the import in your production code (main.py):

    1. instead of import datetime from the module datetime I add the import of the module datetime:
    # following are my imports
    import datetime
    from datetime import date, timedelta
    
    # this was your import
    #from datetime import date, timedelta, datetime
    
    1. to reflect the changes in the import, in the code the invocation of the function strptime() has become datetime.datetime.strptime() instead datetime.strptime()
    2. furthermore the invocation of the function today() has become datetime.date.today() instead date.today()

    Description of changes to the file tests.py

    To remain compliant with the production code I have changed the test method code test_with_patch_on_date() with the modification of the path of the patch():

    # this is your patch()
    #with patch("mock_experiment.main.date") as patch_date:
    
    # the following is my patch()
    with patch('mock_experiment.main.datetime.date') as patch_date:
    

    The new code

    So the code of main.py has become the following:

    #main.py
    
    # following are my imports
    import datetime
    from datetime import date, timedelta
    
    # this was your import
    #from datetime import date, timedelta, datetime
    
    def date_distance_from_today(dt: str | date) -> timedelta:
        if not isinstance(dt, date):
            # HERE I HAVE USED datetime.datetime.strptime() instead datetime.strptime() 
            dt = datetime.datetime.strptime(dt, "%Y-%m-%d").date()
        # HERE I HAVE USED datetime.date.today() instead date.today()
        return datetime.date.today() - dt
    

    while the code of the test file has become:

    import unittest
    from datetime import date, timedelta
    from unittest.mock import patch
    
    from mock_experiment import main
    
    class MyTestCase(unittest.TestCase):
    
        def test_normal(self):  # passes fine today, Jan 10
            assert main.date_distance_from_today(date(2025, 1, 1)) == timedelta(9)
    
        def test_normal_2(self):  # passes fine today, Jan 10
            assert main.date_distance_from_today("2025-01-01") == timedelta(9)
    
        def test_with_patch_on_date(self): # exception thrown, but now pass
            # this is your patch()
            #with patch("mock_experiment.main.date") as patch_date:
    
            # the following is my patch()
            with patch('mock_experiment.main.datetime.date') as patch_date:
                patch_date.today.return_value = date(2025, 1, 2)
                assert main.date_distance_from_today(date(2025, 1, 1)) == timedelta(1)
    
    if __name__ == '__main__':
        unittest.main()
    

    With these modification the 3 tests pass and this is the output on my system:

    ...
    ----------------------------------------------------------------------
    Ran 3 tests in 0.003s
    
    OK
    

    Note. I don't have used pytest; I have used the module unittest, so the test functions in my code are methods of the Test Class MyTestCase.