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