Mocking a formatted datetime.datetime.now() - python

I have this below function and tried pytesting it using mock as per the examples I found here. But unfortunately I am getting an AssertionError.
def get_print_date():
now = datetime.datetime.now()
return now.strftime("%d/%m/%Y %H:%M:%S")
This is my pytest code:
import datetime
from unittest.mock import MagicMock
FAKE_NOW = datetime.datetime(2022, 10, 21, 22, 22, 22)
#fake_now = FAKE_NOW.strftime("%d/%m/%Y %H:%M:%S")
#pytest.fixture()
def mock_print_date(monkeypatch):
print_date_mock = MagicMock(wraps = datetime.datetime)
print_date_mock.now.return_value = FAKE_NOW
monkeypatch.setattr(datetime, "datetime", print_date_mock)
def test_get_print_date(mock_print_date):
assert get_print_date == FAKE_NOW
I also tried using strftime to the constant (to match with the exact format of the return value of my function) which I commented too because it also output for an AssertionError. Any idea what's wrong with it?

From a quick look, you're asserting a function to a datetime object, i.e. assert get_print_date == FAKE_NOW, without calling the function. Doing the latter, in addition to changing the return type of the function from string to datetime, passes the test:
import datetime
from unittest.mock import MagicMock
import pytest
def get_print_date():
now = datetime.datetime.now()
return now
FAKE_NOW = datetime.datetime(2022, 10, 21, 22, 22, 22)
#pytest.fixture()
def mock_print_date(monkeypatch):
print_date_mock = MagicMock(wraps = datetime.datetime)
print_date_mock.now.return_value = FAKE_NOW
monkeypatch.setattr(datetime, "datetime", print_date_mock)
def test_get_print_date(mock_print_date):
assert get_print_date() == FAKE_NOW

Related

Force aware time in python

Is there any way to force all time objects in Python
to be aware, perhaps generating an error if I try to
generate a naïve object?
Is there a way to tell whether a time object is naïve?
Trusting code to be non-naïve is not working for me
and I'm getting bitten.
To tell if a datetime object is aware check its tzinfo attribute, as per the datetime doc says:
A datetime object d is aware if both of the following hold:
d.tzinfo is not None
d.tzinfo.utcoffset(d) does not return None
Otherwise, d is naive.
A time object t is aware if both of the following hold:
t.tzinfo is not None
t.tzinfo.utcoffset(None) does not return None.
As to how to force all datetimes to not be naive, there is a problem. Sometimes one simply does not have the timezone info.
Why not override datetime.datetime.__new__ in a subclass?
Notice how the method datetime.datetime.__new__ checks the timezone argument before creating new instances by calling the module level function:
# cpython/Lib/datetime.py
def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
So we can invoke a similar check before calling datetime.datetime.__new__ in our subclass
import datetime
class AwareDate(datetime.datetime):
def __new__(cls, *args, **kwargs):
if not isinstance(kwargs.get('tzinfo', None), datetime.tzinfo):
raise ValueError('MUST BE TZ AWARE !')
return datetime.datetime.__new__(cls, *args, **kwargs)
Some quick tests
normaldate = datetime.datetime(2020, 12, 20, tzinfo=datetime.timezone.utc)
awaredate = AwareDate(2020, 12, 20, tzinfo=datetime.timezone.utc)
assert awaredate == normaldate
normaldate = datetime.datetime(2020, 12, 20)
awaredate = AwareDate(2020, 12, 20) # ValueError: MUST BE TZ AWARE!

Sub-Class Python datetime Object

I am attempting to sub-class a datetime object to add more methods/utility functions:
from datetime import datetime
import pandas as pd
from pandas.tseries.offsets import BDay
class DateTime(datetime):
def __new__(cls, dt=None, **kwargs):
if dt is None:
cls._datetime = datetime.today()
elif isinstance(dt, datetime):
cls._datetime = dt
else:
cls._datetime = pd.to_datetime(dt, **kwargs)
return datetime.__new__(cls, cls._datetime.year, cls._datetime.month, cls._datetime.day,
cls._datetime.hour, cls._datetime.minute, cls._datetime.second,
cls._datetime.microsecond, cls._datetime.tzinfo)
def datetime(self):
return self._datetime
I have a simple function which bumps the date by n business days and I create two variables; today and bumped:
def date_add(start_date, days=5):
return DateTime(start_date + BDay(days))
today = DateTime()
bumped = date_add(today, days=5)
Assuming today is the 22nd May 2020 and I print the two variables, I see the numbers I expect:
print(today)
print(bumped)
2020-05-22 00:48:00.760808
2020-05-29 00:48:00.760808
However, if I look at the datetime method, I see the value of the bumped item in both instances:
today.datetime()
Out[12]: DateTime(2020, 5, 29, 0, 48, 0, 760808)
bumped.datetime()
Out[13]: DateTime(2020, 5, 29, 0, 48, 0, 760808)
I appreciate what I am doing is wrong and I probably shouldn't attempt to subclass the datetime object anyway but what is wrong here?
I think you want something like:
import datetime
class DateTime:
def __init__(self, some_datetime=None):
if some_datetime is None:
some_datetime = datetime.datetime.today()
self._datetime = some_datetime
def next_day(self):
return DateTime(self._datetime + datetime.timedelta(1))
print(DateTime(datetime.datetime(1,2,3))._datetime)
print(DateTime(datetime.datetime(1,2,3)).next_day()._datetime)
However you might want to ask if it really makes sense to encapsulate the datetime like that. It would be much simpler to just do:
def next_day(some_datetime):
return some_datetime + datetime.timedelta(1)

python - property() in a Class use problem, setter shows TypeError

I'm coding a pybite on codechalleng.es and totally run out of ideas.
Problem is as follows:
task description
I need to write a class that returns boolean True/False whether the promotion has expired or not.
It's tested by invoking a property getter promotion.expired (see tests at the very end)
from datetime import datetime, timedelta
NOW = datetime.now() # defined here for test compatilibity (time)
class Promo:
""" docstring hehe"""
def __init__(self, name, expires):
self.name = name
self.setpromo(expires)
def checkpromo(self):
return self.expired
def setpromo(self, value):
self.expired = value < datetime.now()
expired = property(checkpromo, setpromo)
test_time = NOW + timedelta(days=1)
promotion_10 = Promo('promotion 10%', test_time)
Now it throws an error where i can't compare value in setter vs. datetime object. In my opinion as we pass test_time (a datetime object) into setter, it should be able to compare datetime vs. datetime - instead the program says it's a bool. Not sure how to proceed. Thank you for any insight.
Traceback (most recent call last):
File "/tmp/simple_property.py", line 24, in <module>
promotion_10 = Promo('promotion 10%', test_time)
File "/tmp/simple_property.py", line 10, in __init__
self.setpromo(expires)
File "/tmp/simple_property.py", line 17, in setpromo
self.expired = value < datetime.now()
File "/tmp/simple_property.py", line 17, in setpromo
self.expired = value < datetime.now()
TypeError: '<' not supported between instances of 'bool' and 'datetime.datetime'
tests that needs to pass:
from datetime import timedelta
import inspect
from simple_property import Promo, NOW
def test_promo_expired():
past_time = NOW - timedelta(seconds=3)
twitter_promo = Promo('twitter', past_time)
assert twitter_promo.expired
def test_promo_not_expired():
future_date = NOW + timedelta(days=1)
newsletter_promo = Promo('newsletter', future_date)
assert not newsletter_promo.expired
def test_uses_property():
assert 'property' in inspect.getsource(Promo)
I've finally found the issue - it might be interesting to someone experiencing such setter/property() behaviour. (Please correct me if im not supposed to answer my own question/close the topic etc.)
In my example, the property expired has the same name as private attribute self.expired.
After changing the code to (just adding one underscore in self._expired - changing the name so it does not overlap with property() name):
def checkpromo(self):
return self._expired
def setpromo(self, value):
self._expired = value < datetime.now()
It's worked! thank you
EDIT - adding elegant decorator way of solving the task (provided as an answer once submitted my task to codechalleng.es)
from datetime import datetime
NOW = datetime.now()
class Promo:
def __init__(self, name, expires=NOW):
self.name = name
self.expires = expires
#property
def expired(self):
return datetime.now() > self.expires

Appengine datastore raising badValueErro on datetime, might be caused by monkeypatch

I'm getting the following error message:
BadValueError: Expected datetime, got datetime.datetime(2013, 4, 19, 19, 48, 2, 566558)
The error is raised in ndb's model.py line 1190, here's a snippet of code in there.
def _validate(self, value):
if not isinstance(value, datetime.datetime):
raise datastore_errors.BadValueError('Expected datetime, got %r' % (value,))
This DateTime is Passed into a model like this:
#user is a parameter that's passed into the function this code is inside
#the same goes for lifetime, which is a number of seconds
token = os.urandom(16).encode("hex")
expires=datetime.datetime.now() + datetime.timedelta(seconds=lifetime)
instance = cls(token=token, user=user, expires=expires)
However, I'm monkeypatching datetime.datetime using pytest's monkeypatch argument.
newnow = datetime.datetime.now()
later = newnow + datetime.timedelta(seconds=10)
class fake_dt(datetime.datetime):
#classmethod
def now(cls):
return newnow
monkeypatch.setattr(datetime,"datetime", fake_dt)
#after that, I call my model creation function for the test.
Could the fact that I'm monkeypatching datetime.datetime be the problem? and how do I get around that? I need to make sure that datetime.datetime.now() returns the date I captured at the start of my test.

Trying to mock datetime.date.today(), but not working

Can anyone tell me why this isn't working?
>>> import mock
>>> #mock.patch('datetime.date.today')
... def today(cls):
... return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)
Perhaps someone could suggest a better way?
Another option is to use
https://github.com/spulec/freezegun/
Install it:
pip install freezegun
And use it:
from freezegun import freeze_time
#freeze_time("2012-01-01")
def test_something():
from datetime import datetime
print(datetime.now()) # 2012-01-01 00:00:00
from datetime import date
print(date.today()) # 2012-01-01
It also affects other datetime calls in method calls from other modules:
other_module.py:
from datetime import datetime
def other_method():
print(datetime.now())
main.py:
from freezegun import freeze_time
#freeze_time("2012-01-01")
def test_something():
import other_module
other_module.other_method()
And finally:
$ python main.py
# 2012-01-01
For what it's worth, the Mock docs talk about datetime.date.today specifically, and it's possible to do this without having to create a dummy class:
https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
... mock_date.today.return_value = date(2010, 10, 8)
... mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
... assert mymodule.date.today() == date(2010, 10, 8)
... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...
There are a few problems.
First of all, the way you're using mock.patch isn't quite right. When used as a decorator, it replaces the given function/class (in this case, datetime.date.today) with a Mock object only within the decorated function. So, only within your today() will datetime.date.today be a different function, which doesn't appear to be what you want.
What you really want seems to be more like this:
#mock.patch('datetime.date.today')
def test():
datetime.date.today.return_value = date(2010, 1, 1)
print datetime.date.today()
Unfortunately, this won't work:
>>> test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'
This fails because Python built-in types are immutable - see this answer for more details.
In this case, I would subclass datetime.date myself and create the right function:
import datetime
class NewDate(datetime.date):
#classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
And now you could do:
>>> datetime.date.today()
NewDate(2010, 1, 1)
I guess I came a little late for this but I think the main problem here is that you're patching datetime.date.today directly and, according to the documentation, this is wrong.
You should patch the reference imported in the file where the tested function is, for example.
Let's say you have a functions.py file where you have the following:
import datetime
def get_today():
return datetime.date.today()
then, in your test, you should have something like this
import datetime
import unittest
from functions import get_today
from mock import patch, Mock
class GetTodayTest(unittest.TestCase):
#patch('functions.datetime')
def test_get_today(self, datetime_mock):
datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
value = get_today()
# then assert your thing...
Hope this helps a little bit.
Here's another way to mock datetime.date.today() with an added bonus that the rest of datetime functions continue to work, as the mock object is configured to wrap the original datetime module:
from unittest import mock, TestCase
import foo_module
class FooTest(TestCase):
#mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
def test_something(self, mock_datetime):
# mock only datetime.date.today()
mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
# other calls to datetime functions will be forwarded to original datetime
Note the wraps=datetime argument to mock.patch() – when the foo_module uses other datetime functions besides date.today() they will be forwarded to the original wrapped datetime module.
To add to Daniel G's solution:
from datetime import date
class FakeDate(date):
"A manipulable date replacement"
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
This creates a class which, when instantiated, will return a normal datetime.date object, but which is also able to be changed.
#mock.patch('datetime.date', FakeDate)
def test():
from datetime import date
FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
return date.today()
test() # datetime.date(2010, 1, 1)
The easiest way for me is doing this:
import datetime
from unittest.mock import Mock, patch
def test():
datetime_mock = Mock(wraps=datetime.datetime)
datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
with patch('datetime.datetime', new=datetime_mock):
assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)
CAUTION for this solution: all functionality from datetime module from the target_module will stop working.
I faced the same situation a couple of days ago, and my solution was to define a function in the module to test and just mock that:
def get_date_now():
return datetime.datetime.now()
Today I found out about FreezeGun, and it seems to cover this case beautifully
from freezegun import freeze_time
import datetime
import unittest
#freeze_time("2012-01-14")
def test():
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
You can use the following approach, based on Daniel G solution. This one has advantage of not breaking type checking with isinstance(d, datetime.date).
import mock
def fixed_today(today):
from datetime import date
class FakeDateType(type):
def __instancecheck__(self, instance):
return isinstance(instance, date)
class FakeDate(date):
__metaclass__ = FakeDateType
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
#staticmethod
def today():
return today
return mock.patch("datetime.date", FakeDate)
Basically, we replace C-based datetime.date class with our own python subclass, that produces original datetime.date instances and responds to isinstance() queries exactly as native datetime.date.
Use it as context manager in your tests:
with fixed_today(datetime.date(2013, 11, 22)):
# run the code under test
# note, that these type checks will not break when patch is active:
assert isinstance(datetime.date.today(), datetime.date)
Similar approach can be used to mock datetime.datetime.now() function.
CPython actually implements the datetime module using both a pure-Python Lib/datetime.py and a C-optimized Modules/_datetimemodule.c. The C-optimized version cannot be patched but the pure-Python version can.
At the bottom of the pure-Python implementation in Lib/datetime.py is this code:
try:
from _datetime import * # <-- Import from C-optimized module.
except ImportError:
pass
This code imports all the C-optimized definitions and effectively replaces all the pure-Python definitions. We can force CPython to use the pure-Python implementation of the datetime module by doing:
import datetime
import importlib
import sys
sys.modules["_datetime"] = None
importlib.reload(datetime)
By setting sys.modules["_datetime"] = None, we tell Python to ignore the C-optimized module. Then we reload the module which causes the import from _datetime to fail. Now the pure-Python definitions remain and can be patched normally.
If you're using Pytest then include the snippet above in conftest.py and you can patch datetime objects normally.
We can use pytest-mock (https://pypi.org/project/pytest-mock/) mocker object to mock datetime behaviour in a particular module
Let's say you want to mock date time in the following file
# File path - source_dir/x/a.py
import datetime
def name_function():
name = datetime.now()
return f"name_{name}"
In the test function, mocker will be added to the function when test runs
def test_name_function(mocker):
mocker.patch('x.a.datetime')
x.a.datetime.now.return_value = datetime(2019, 1, 1)
actual = name_function()
assert actual == "name_2019-01-01"
Generally speaking, you would have datetime or perhaps datetime.date imported into a module somewhere. A more effective way of mocking the method would be to patch it on the module that is importing it. Example:
a.py
from datetime import date
def my_method():
return date.today()
Then for your test, the mock object itself would be passed as an argument to the test method. You would set up the mock with the result value you want, and then call your method under test. Then you would assert that your method did what you want.
>>> import mock
>>> import a
>>> #mock.patch('a.date')
... def test_my_method(date_mock):
... date_mock.today.return_value = mock.sentinel.today
... result = a.my_method()
... print result
... date_mock.today.assert_called_once_with()
... assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today
A word of warning. It is most certainly possible to go overboard with mocking. When you do, it makes your tests longer, harder to understand, and impossible to maintain. Before you mock a method as simple as datetime.date.today, ask yourself if you really need to mock it. If your test is short and to the point and works fine without mocking the function, you may just be looking at an internal detail of the code you're testing rather than an object you need to mock.
It's possible to mock functions from datetime module without adding side_effects
import mock
from datetime import datetime
from where_datetime_used import do
initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
mocked_dt.now.return_value = initial_date
do()
The best approach for me is a combination of #Daniel G and #frx08 solutions:
class Test_mock_date:
class NewDate(datetime.datetime):
#classmethod
def now(cls, tz=None):
return cls(2021, 5, 12)
def test_mock_date(self):
with patch('datetime.datetime', new = self.NewDate):
assert datetime.datetime.now() == datetime.datetime(2021, 5, 12, 0, 0)
You can take a look at the following medium article I wrote with different examples about how to use MagicMock https://medium.com/#camposer/d2113513b365
For those of you using pytest with pytest-mock (more info on pytest-mock at the end) here is how I mocked datetime.datetime.now() which is very similar to the original question.
test_get_now(mocker):
datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)
now == function_being_tested() # run function
assert now == datetime.datetime(2019,3,11,6,2,0,0)
Essentially the mock has to be set to return the specified date. You aren't able to patch over datetime's object directly.
Pytest-mock is a library that makes a mock object a fixture. More detail can be found here
Maybe you could use your own "today()" method that you will patch where needed. Example with mocking utcnow() can be found here: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
I implemented #user3016183 method using a custom decorator:
def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
"""decorator used to change datetime.datetime.now() in the tested function."""
def retfunc(self):
with mock.patch('mymodule.datetime') as mock_date:
mock_date.now.return_value = newNow
mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
func(self)
return retfunc
I thought that might help someone one day...
Several solutions are discussed in http://blog.xelnor.net/python-mocking-datetime/. In summary:
Mock object - Simple and efficient but breaks isinstance() checks:
target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
patched.now.return_value = target
print(datetime.datetime.now())
Mock class
import datetime
import mock
real_datetime_class = datetime.datetime
def mock_datetime_now(target, dt):
class DatetimeSubclassMeta(type):
#classmethod
def __instancecheck__(mcs, obj):
return isinstance(obj, real_datetime_class)
class BaseMockedDatetime(real_datetime_class):
#classmethod
def now(cls, tz=None):
return target.replace(tzinfo=tz)
#classmethod
def utcnow(cls):
return target
# Python2 & Python3 compatible metaclass
MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})
return mock.patch.object(dt, 'datetime', MockedDatetime)
Use as:
with mock_datetime_now(target, datetime):
....
I made this work by importing datetime as realdatetime and replacing the methods I needed in the mock with the real methods:
import datetime as realdatetime
#mock.patch('datetime')
def test_method(self, mock_datetime):
mock_datetime.today = realdatetime.today
mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)
You can mock datetime using this:
In the module sources.py:
import datetime
class ShowTime:
def current_date():
return datetime.date.today().strftime('%Y-%m-%d')
In your tests.py:
from unittest import TestCase, mock
import datetime
class TestShowTime(TestCase):
def setUp(self) -> None:
self.st = sources.ShowTime()
super().setUp()
#mock.patch('sources.datetime.date')
def test_current_date(self, date_mock):
date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
current_date = self.st.current_date()
self.assertEqual(current_date, '2019-10-01')
For those using patchers in a test class, here's how I'm successfully patching datetime functionality:
from datetime import datetime
import unittest
from unittest.mock import Mock, patch
# Replace with the proper path to the module you would
# like datetime to be mocked
from path.to.my_module
class MyTestCases(unittest.TestCase):
def setUp(self):
"""execute on class instantiation"""
# Record both times at the same moment
self.dt_now, self.dt_utcnow = datetime.now(), datetime.utcnow()
# After retrieving real (or hardcoded datetime values),
# proceed to mock them in desired module
self.patch_datetime_functions()
def patch_datetime_functions(self) -> None:
"""
Patch datetime.now() and datetime.utcnow() to prevent issues when
comparing expected dates
"""
# Create a patcher
self.patcher_dt = patch(
'path.to.my_module'
)
# Start but make sure cleanup always occurs
self.patcher_dt.start()
self.addCleanup(self.patcher_dt.stop)
# Perform the actual patch – use lambdas as mock functions
datetime_mock = Mock(wraps=datetime)
datetime_mock.now.return_value = self.dt_now
datetime_mock.utcnow.return_value = self.dt_utcnow
my_module.datetime = datetime_mock
# Here's what it will look like when testing:
def some_test(self):
curr_dt = self.dt_now
returned_dt = my_module.datetime.utcnow()
# Compare the dates
self.assertEqual(curr_dt, returned_dt,
'Datetime values should be equal'
)
Minimal working example with monkeypatch
This solution uses monkeypatch from the https://pypi.org/project/pytest-mock/ package.
Features:
mock only datetime.today(), but datetime.now() still works as expected
mock only in a specific scope (i.e. block)
import sys
from datetime import datetime
MOCKED_DATETIME_TODAY = datetime(1900, 1, 1, 0, 0, 0)
class MockedDatetime(datetime):
#classmethod
def today(cls):
return MOCKED_DATETIME_TODAY
def test_mock_datetime_today(monkeypatch):
"""Only datetime.today() is mocked and returns some date in 1900. datetime.now() returns still the current date."""
with monkeypatch.context() as mpc:
mpc.setattr(sys.modules[__name__], 'datetime', MockedDatetime)
assert datetime.today() == MOCKED_DATETIME_TODAY # datetime.today() mocked
assert datetime.now() > MOCKED_DATETIME_TODAY # datetime.now() not mocked
assert datetime.today() > MOCKED_DATETIME_TODAY # not mocked anymore

Categories