How to mock python's datetime.now() in a class method for unit testing?

PythonDjangoUnit TestingTestingMocking

Python Problem Overview


I'm trying to write tests for a class that has methods like:

import datetime
import pytz

class MyClass:
    def get_now(self, timezone):
        return datetime.datetime.now(timezone)

    def do_many_things(self, tz_string='Europe/London'):
        tz = pytz.timezone(tz_string)
        localtime_now = self.get_now(tz)
        ...
        return things

I want to test it, and to do so I need to make sure that the datetime.datetime.now() call returns something predictable.

I've been reading lots of examples of using Mock in tests, but haven't found anything quite like what I need, and I can't work out how to use it in my tests.

I separated the get_now() method out in case it's easier to mock that, instead of datetime.datetime.now(), but I'm still stumped. Any thoughts on how to write UnitTests for this using Mock? (This is all in Django, fwiw; I'm not sure if this makes a difference in this case.)

Python Solutions


Solution 1 - Python

You could use freezegun :

from freezegun import freeze_time

def test():
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
    with freeze_time("2012-01-14"):
        assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)

It basically mocks datetime module calls.

Solution 2 - Python

You'd create a function that returns a specific datetime, localized to the timezone passed in:

import mock

def mocked_get_now(timezone):
    dt = datetime.datetime(2012, 1, 1, 10, 10, 10)
    return timezone.localize(dt)

@mock.patch('path.to.your.models.MyClass.get_now', side_effect=mocked_get_now)
def your_test(self, mock_obj):
    # Within this test, `MyClass.get_now()` is a mock that'll return a predictable
    # timezone-aware datetime object, set to 2012-01-01 10:10:10.

That way you can test if the resulting timezone-aware datetime is correctly being handled; results elsewhere should show the correct timezone but will have a predictable date and time.

You use the mocked_get_now function as a side-effect when mocking get_now; whenever code calls get_now the call is recorded by mock, and mocked_get_now is called, and it's return value used as the value returned to the caller of get_now.

Solution 3 - Python

I'm using date, but the same idea should work for datetime:

class SpoofDate(date):
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

...

from mock import patch

@patch('some.module.date', SpoofDate)
def testSomething(self):
    SpoofDate.today = classmethod(lambda cls : date(2012, 9, 24))

Where some.module imports date. Patch is replacing the imported date with SpoofDate, which you can then redefine to do whatever you want.

Solution 4 - Python

I would use the helpers from the 'testfixtures' package to mock out the datetime class you're calling now() on:

http://packages.python.org/testfixtures/datetime.html#datetimes

That way, you can test all the cases you have, all the time.

Solution 5 - Python

Having asked this question originally...

As @Jocelyn delalande suggested, I've been happily using freezegun for years now.

Another option is python-libfaketime, which can be much faster than freezegun, but doesn't work on Windows and sounds a bit fiddly.

A newer option is time-machine, introduced in this blog post that compares the three options.

Solution 6 - Python

Here's the most elegant way to do this IMO:

import datetime
from unittest import mock

test_now = datetime.datetime(1856, 7, 10)
with mock.patch('datetime.datetime', wraps=datetime.datetime) as dt:
    print(dt.now()) # calls the real thing
    dt.now.return_value = test_now
    print(dt.now()) # calls the mocked value

The advantages here is that you needn't patch the datetime module via the tested module's local attribute, it supports calling unmocked methods, and it doesn't require any external imports.

Solution 7 - Python

> Using patch of unittest.mock

from unittest.mock import patch

@patch('MyClass.datetime')
def test_foo(self, mock_datetime):
    mock_datetime.datetime.now.return_value = datetime.datetime(2019, 5, 7) #SOME_MOCKED_DATE

Note that we're overriding datetime module that is imported only in our class

The class for which we are writing the test:

import datetime

class MyClass:
    def foo():
       localtime_now = datetime.datetime.now(timezone)

We need not have to separate it as get_now() method just to make it easier to mock.

Solution 8 - Python

If you don't want to install anything this is the simplest way. Simply use, Mock class -

class NewDt(datetime.date):
    @classmethod
     def now(cls):
           return datetime.datetime.strptime('2020-07-10 05:20:20', '%Y-%m-%d %H:%M:%S')

And use this patch before mock function

 @mock.patch('module path', NewDt)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhil GyfordView Question on Stackoverflow
Solution 1 - PythonJocelyn delalandeView Answer on Stackoverflow
Solution 2 - PythonMartijn PietersView Answer on Stackoverflow
Solution 3 - PythonIzkataView Answer on Stackoverflow
Solution 4 - PythonChris WithersView Answer on Stackoverflow
Solution 5 - PythonPhil GyfordView Answer on Stackoverflow
Solution 6 - PythonblaisView Answer on Stackoverflow
Solution 7 - PythonRiya JohnView Answer on Stackoverflow
Solution 8 - PythonRaj SharmaView Answer on Stackoverflow