Mocking default=timezone.now for unit tests

微笑、不失礼 提交于 2019-12-03 23:03:08

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 the default kwarg to the real timezone.now function.

The solution is the following:

class MyModel(models.Model):
    timestamp = models.DateTimeField(default=lambda: timezone.now())

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--

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--

@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

would let you write--

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

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

There is another easy way to do the above thing.

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.

Looks like you are patching timezone in the wrong place.

Assuming your User model lives in myapp\models.py and you want to test save() in that 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 are patching timezone locally, and it has no effect on your test, since module myapp\models.py already has a reference to the real timezone and it looks like our patching had no effect.

Try patching 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)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!