I'm trying to write unit tests for a django app that does a lot of datetime operations. I have installed mock to monkey patch django's timezone.now
for my tests.
While I am able to successfully mock timezone.now
when it is called normally (actually calling timezone.now()
in my code, I am not able to mock it for models that are created with a DateTimeField
with default=timezone.now
.
I have a User
model that contains the following:
from django.utils import timezone
...
timestamp = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(default=timezone.now)
...
def save(self, *args, **kwargs):
if kwargs.pop('modified', True):
self.modified = timezone.now()
super(User, self).save(*args, **kwargs)
My unit test looks like this:
from django.utils import timezone
def test_created(self):
dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
with patch.object(timezone, 'now', return_value=dt):
user = User.objects.create(username='test')
self.assertEquals(user.modified, dt)
self.assertEquals(user.timestamp, dt)
assertEquals(user.modified, dt)
passes, but assertEquals(user.timestamp, dt)
does not.
How can I mock timezone.now
so that even default=timezone.now
in my models will create the mock time?
Edit
I know that I could just change my unit test to pass a timestamp
of my choice (probably generated by the mocked timezone.now
)... Curious if there is a way that avoids that though.
There is another easy way to do the above thing.
This is the best way to mock timezone.now.
Looks like you are patching timezone in the wrong place.
Assuming your
User
model lives inmyapp\models.py
and you want to testsave()
in that file. The problem is that when youfrom django.utils import timezone
at the top, it imports it fromdjango.utils
. In your test you are patchingtimezone
locally, and it has no effect on your test, since modulemyapp\models.py
already has a reference to the realtimezone
and it looks like our patching had no effect.Try patching
timezone
frommyapp\models.py
, something like:I just ran into this issue myself. The problem is that models are loaded before mock has patched the timezone module, so at the time the expression
default=timezone.now
is evaluated, it sets thedefault
kwarg to the realtimezone.now
function.The solution is the following:
Here's a method you can use that doesn't require altering your non-test code. Just patch the
default
attributes of the fields you want to affect. For example--You can write helper functions to make this less verbose. For example, the following code--
would let you write--
Similarly, you can write helper context managers to patch multiple fields at once.