Patching Datetime Objects in Python

Saturday, September 23, 2017

Problem: Patching a datetime object

# Code for this blog post:
git clone https://github.com/wgwz/patching-datetime-objects.git

# Checkout the code for section 1 and 2 respectively
git checkout 1a
git checkout 2a

# Running the code:
python3 lib.py

Why can patching a datetime object be a problem?

Python places some restrictions patching builtin's, and datetime is a builtin. I won't say that patching datetime objects is incredibly important. But it is definitely useful, for the sake of thorough unit testing. i.e. If you hard-code a date or time in your tests, without patching, your tests will fail when they get run on a different day.

This post will demonstrate the pitfall I ran into, and the way I currently patch the datetime object. Presumably the pitfalls in patching other builtin objects are similar, and the method here can be applied to those as well.

1. How does patching datetime.utcnow fail?

When you patch datetime.utcnow directly.

@patch('__main__.datetime.utcnow')
def test_get_function(self, mock_utcnow):
    mock_utcnow.return_value = 'pAtChEd'
    assert get() == 'pAtChEd'

To me this seems like a pretty reasonable approach. But alas, we cannot patch builtins. When you run this code, we get the following error message:

TypeError: can't set attributes of built-in/extension type 'datetime.datetime'

2. How to solve the problem:

Patch the datetime object instead.

@patch('__main__.datetime')
def test_get_function(self, mock_dt):
    mock_dt.utcnow.return_value = 'pAtChEd'
    assert get() == 'pAtChEd'

The solution is mock the datetime object in it's entirety. Then specify that the mock datetime object should return a given value, when the method utcnow is called:

mock_dt.utcnow.return_value = 'pAtChEd'

Wrapping up:

And there we have it. When you run into this type of error during mocking, patch at a higher-level!

Happy unit-testing! 🐐