模拟default = timezone.now进行单元测试

dge*_*gel 17 python django unit-testing mocking python-mock

我正在尝试为执行大量日期操作的django应用程序编写单元测试.我为我的测试安装了模拟猴子补丁django的timezone.now.

虽然我能够成功地嘲笑timezone.now时,它通常被称为(实际调用timezone.now()在我的代码,我不能嘲笑它为与一个创建的模型DateTimeFielddefault=timezone.now.


我有一个User包含以下内容的模型:

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)
Run Code Online (Sandbox Code Playgroud)

我的单元测试看起来像这样:

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)
Run Code Online (Sandbox Code Playgroud)

assertEquals(user.modified, dt)通过,但assertEquals(user.timestamp, dt)没有.

我怎么能模仿timezone.now,即使default=timezone.now在我的模型中也会创建模拟时间?


编辑

我知道我可以改变我的单元测试以通过timestamp我的选择(可能是由模拟生成的timezone.now)...好奇如果有一种方法可以避免这种情况.

Jor*_*son 11

我自己也遇到过这个问题.问题是在mock修补了时区模块之前加载了模型,因此在default=timezone.now计算表达式时,它default会将kwarg设置为实际timezone.now函数.

解决方案如下:

class MyModel(models.Model):
    timestamp = models.DateTimeField(default=lambda: timezone.now())
Run Code Online (Sandbox Code Playgroud)

  • 不要那样做!django无法序列化lambda,它将在makemigrations期间以异常结束。而是在utils包def now()中创建包装函数:return timezone.now()并在各处使用它 (4认同)

cje*_*nek 10

这是您可以使用的方法,不需要更改非测试代码.只需修补default您想要影响的字段的属性.例如 -

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

您可以编写辅助函数以使其更简洁.例如,以下代码 -

@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
Run Code Online (Sandbox Code Playgroud)

会让你写 -

with patch_field(User, 'timestamp', dt):
    # Your code here
Run Code Online (Sandbox Code Playgroud)

同样,您可以编写辅助上下文管理器来一次修补多个字段.


小智 6

还有另一种简单的方法可以完成上述事情。

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)
Run Code Online (Sandbox Code Playgroud)

这是模拟 timezone.now 的最佳方式。

  • 这似乎不适用于 Django 2.1.5 和 python 3.6.7 (6认同)