How to mock python's datetime.now() in a class method for unit testing?
Solution 1
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
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
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
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 5
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.
Phil Gyford
Web developer/designer/dabbler. Ran the ten year Diary of Samuel Pepys project, among other things.
Updated on January 06, 2022Comments
-
Phil Gyford over 2 years
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 ofdatetime.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.) -
zakiakhmad about 9 years@FearlessFuture did you just replace
date
withdatetime
? Can you write the implementation indatetime
. -
FearlessFuture about 9 years@zakiakhmad, below is an example of what I did: class StubDate(datetime.datetime): pass @mock.patch("friend.datetime.datetime", StubDate) def test_generate_date(self): # Make datetime.datetime.now return a fixed value StubDate.now = classmethod(lambda cls: datetime.datetime(2015, 03, 11, 11, 01)) self.assertEqual( self.friend_obj.generate_date(input), datetime.datetime(2015, 03, 11, 11, 01)) > Blockquote
-
zakiakhmad about 9 years@FearlessFuture thanks a lot! I wrote the gist here for a better readability. gist.github.com/za/2a217c47582737f88259
-
Avinash Raj over 8 years@MartijnPieters can't able to import
mock
-
Martijn Pieters over 8 years@AvinashRaj
mock
is an add-on package, install it with pip. With Python 3 it is included asunittest.mock
. -
Montaro over 7 yearsThanks, It works! However the first assertion did not because the month token was not accepted in this format:
01
butdatetime.datetime(2012, 1, 14)
works. -
Andrey Belyak about 5 yearsfreezegun is slow, especially if you test logic with multiple calls to datetime.now()
-
Andrey Belyak about 5 yearsThank you, works much better and simpler than any other solutions