Mocking default=timezone.now用于单元测试

2024-09-27 20:17:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试为一个django应用程序编写单元测试,该应用程序执行很多datetime操作。我已经为我的测试安装了mock到猴子补丁django的timezone.now

当正常调用timezone.now(实际上在代码中调用timezone.now())时,我能够成功地模拟它,但对于使用default=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)

我的单元测试如下:

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)通过,但assertEquals(user.timestamp, dt)不通过。

如何模拟timezone.now,以便即使是模型中的default=timezone.now也能创建模拟时间?


编辑

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


Tags: django模型self应用程序defaultdt单元测试now
3条回答

我自己也碰到过这个问题。问题是mock在修补时区模块之前加载模型,因此在计算表达式default=timezone.now时,它将defaultkwarg设置为真正的timezone.now函数。

解决方案如下:

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

有另一个简单的方法来做以上的事情。

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)

这是模拟时区的最好方法。现在。

这里有一个不需要修改非测试代码的方法。只需修补要影响的字段的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

您可以编写助手函数来减少冗长。例如,下面的代码--

@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

会让你写——

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

类似地,您可以编写助手上下文管理器来同时修补多个字段。

相关问题 更多 >

    热门问题