Mocking datetime.now and datetime.today in Python

A couple of years ago, I dealt with a network device that didn’t add a year to the string that described the last time an interface flapped, e.g. THU OCT 02 14:07:47. We needed the exact date to figure out which interfaces had been down for X amount of days. I wrote a script that completed the datetime object by calculating the most recent year an incomplete datetime string occurred on a specific weekday.

I also wrote a test in case I was going to modify the script in the future.

But recently one of the tests started failing:

    def test_find_date_from_incomplete_string(partial_date, expected):
>       assert parse_date_from_incomplete_string(partial_date) == expected
E       assert datetime.datetime(2021, 1, 26, 0, 16, 32) == datetime.datetime(2016, 1, 26, 0, 16, 32)
E         +datetime.datetime(2021, 1, 26, 0, 16, 32)
E         -datetime.datetime(2016, 1, 26, 0, 16, 32)

The script found a more recent date than the one pytest expected, because I used datetime.today().year inside the parse_date_from_incomplete_string function, and that value will obviously change as time moves forward.

How can you stop time in Python?

FreezeGun library—no mocking or patching needed

There’s a popular and well-documented package called FreezeGun; it can freeze time for parts of your code.

$ pip install freezegun

I added the @freeze_time decorator to my test function and gave it the argument 2020-03-03. From that point on, datetime.today().year returns 2020 in perpetuity when it’s called by pytest.

And now the tests pass again:

test_parse_incomplete_date.py::test_find_date_from_incomplete_string[THU OCT 02 14:07:47-expected0] PASSED
...
test_parse_incomplete_date.py::test_find_date_from_incomplete_string[MON FEB 29 13:37:00-expected6] PASSED

Can I solve the problem without an external library?

Yes, you could change the API of your function and use dependency injection.

If you provide today’s date as a function argument you remove the function’s internal dependency on datetime.today().