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'
main.py
I have found a possible solution by the modification of the import in your production code (main.py
):
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
strptime()
has become datetime.datetime.strptime()
instead datetime.strptime()
today()
has become datetime.date.today()
instead date.today()
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:
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
.