How to Mock the Current Date and Time in Python

2020-12-20 The power of time in the palm of my hand!

If you’re testing Python code that relies on the current date or time, you will probably want to mock time to test different scenarios. For example, what happens when you run a certain piece of code on February 29? (A common source of bugs.)

Unfortunately such mocking is not so easy. If you try using unittest.mock to swap functions in the datetime module, you’ll hit a TypeError. For example this code:

import datetime as dt
from unittest import mock

with mock.patch.object(dt.date, 'today', return_value=dt.date(2020, 2, 29)):
    print(dt.date.today())

…will blow up with this TypeError:

$ python example.py
Traceback (most recent call last):
  File "/Users/chainz/.pyenv/versions/3.9.0/lib/python3.9/unittest/mock.py", line 1502, in __enter__
    setattr(self.target, self.attribute, new_attr)
TypeError: can't set attributes of built-in/extension type 'datetime.date'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/chainz/tmp/example.py", line 4, in <module>
    with mock.patch.object(dt.date, 'today', return_value=dt.date(2020, 2, 29)):
  File "/Users/chainz/.pyenv/versions/3.9.0/lib/python3.9/unittest/mock.py", line 1515, in __enter__
    if not self.__exit__(*sys.exc_info()):
  File "/Users/chainz/.pyenv/versions/3.9.0/lib/python3.9/unittest/mock.py", line 1521, in __exit__
    setattr(self.target, self.attribute, self.temp_original)
TypeError: can't set attributes of built-in/extension type 'datetime.date'

This is because the datetime module is built in C, and unittest.mock only supports modifying pure Python objects and functions. So what are the alternatives?

One option is to refactor your code to use dependency injection, as suggested in this post by Haki Benita. But this can be time consuming, changes the layout of the code purely for the tests, and can be a large refactoring to impose on an otherwise working system.

Another option is to use a library that specifically mocks the date and time. These workaround the TypeError issue and also mock all of Python’s date and time functions at once to return consistent values.

One such library is time-machine, which I wrote earlier this year. With it, you can easily mock the current date and time by passing the point in time you’d like to end up at:

import datetime as dt

import time_machine

with time_machine.travel(dt.date(2020, 2, 29)):
    print(dt.date.today())

This works as expected:

$ python example.py
2020-02-29

time-machine was inspired by an earlier library called freezegun, with some improvements in terms of performance and consistency. For more information on the different ways you can use time-machine, see its README on PyPI.

Fin

Happy time travelling,

—Adam


Working on a Django project? Check out my book Speed Up Your Django Tests which covers loads of best practices so you can write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: python