Default Chipping = timezone.now for unit tests

I am trying to write unit tests for a django application that does a lot of datetime operations. I installed mock in the django timezone.now monkey patch for my tests.

While I can successfully mock timezone.now when it is called normally (it actually calls timezone.now() in my code, I cannot simulate it for models created using 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 work.

How can I make fun of timezone.now so that even default=timezone.now in my models creates layout time?


Edit

I know that I can just change my unit test to pass the timestamp of my choice (possibly generated by the mocked timezone.now ) ... I wonder if there is a way that avoids this.

+8
python django unit-testing mocking python-mock
source share
4 answers

I myself came across this question. The problem is that the models load before the mock has fixed the timezone module, so when it evaluates the expression default=timezone.now it sets default kwarg to the actual timezone.now function.

The solution is as follows:

 class MyModel(models.Model): timestamp = models.DateTimeField(default=lambda: timezone.now()) 
+9
source share

Here you can use a method that does not require changing your non-test code. Just correct the default attributes of the fields you want to affect. For example -

 field = User._meta.get_field('timestamp') mock_now = lambda: datetime(2010, 1, 1) with patch.object(field, 'default', new=mock_now): # Your code here 

You can write helper functions to make this less verbose. For example, the following code is

 @contextmanager def patch_field(cls, field_name, dt): field = cls._meta.get_field(field_name) mock_now = lambda: dt with patch.object(field, 'default', new=mock_now): yield 

let you write -

 with patch_field(User, 'timestamp', dt): # Your code here 

Similarly, you can write helper context managers to fix multiple fields at once.

+5
source share

It looks like you are setting the time zone to the wrong place.

Assuming your User model lives in myapp\models.py and you want to test save() in this file. The problem is that when you from django.utils import timezone at the top, it imports it from django.utils . In your test, you localize timezone locally, and this does not affect your test, since the myapp\models.py already has a link to the real timezone , and it seems that our fix did not affect.

Try fixing timezone from myapp\models.py , something like:

 import myapp.models.timezone def test_created(self): with patch('myapp.models.timezone') as mock_timezone: dt = datetime(2010, 1, 1, tzinfo=timezone.utc) mock_timezone.now.return_value = dt assert myapp.models.timezone.now() == dt user = User.objects.create(username='test') self.assertEquals(user.modified, dt) self.assertEquals(user.timestamp, dt) 
0
source share

There is another easy way to do this.

 import myapp.models.timezone from unittest.mock import patch @patch('django.utils.timezone.now') def test_created(self, mock_timezone): dt = datetime(2010, 1, 1, tzinfo=timezone.utc) mock_timezone.return_value = dt user = User.objects.create(username='test') self.assertEquals(user.modified, dt) self.assertEquals(user.timestamp, dt) 

This is the best way to mock timezone.now.

0
source share

All Articles