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