27

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

虽然我能够在timezone.now正常调用它时成功模拟(实际上是timezone.now()在我的代码中调用,但我无法为使用DateTimeFieldwith创建的模型模拟它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我的选择(可能由 mocked 生成timezone.now)...很好奇是否有一种方法可以避免这种情况。

4

4 回答 4

16

这是您可以使用的一种不需要更改非测试代码的方法。只需修补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

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

于 2014-02-10T12:22:09.027 回答
11

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

解决方案如下:

class MyModel(models.Model):
    timestamp = models.DateTimeField(default=lambda: timezone.now())
于 2013-10-03T21:44:14.860 回答
4

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

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)

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

于 2017-08-30T10:11:19.997 回答
2

看起来你在错误的地方修补时区。

假设您的User模型存在myapp\models.py并且您想save()在该文件中进行测试。问题是当你from django.utils import timezone在顶部时,它会从django.utils. 在您的测试中,您正在本地修补timezone,它对您的测试没有影响,因为模块myapp\models.py已经引用了真实的timezone并且看起来我们的修补没有效果。

尝试timezone从修补myapp\models.py,例如:

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)
于 2013-09-20T20:19:39.820 回答