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

时间:2013-09-20 19:58:38

标签: python django unit-testing mocking python-mock

我正在尝试为执行大量日期操作的django应用程序编写单元测试。我为我的测试安装了mock来修补django的timezone.now

虽然我能够在正常调用时成功模拟timezone.now(实际上在我的代码中调用timezone.now(),但我无法模拟使用{{1}创建的模型与DateTimeField


我有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生成的)...好奇,如果有办法可以避免这种情况。

4 个答案:

答案 0 :(得分:12)

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

解决方案如下:

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

答案 1 :(得分:7)

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

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

答案 2 :(得分:1)

还有另一种简单的方法来做上述事情。

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的最好方法。

答案 3 :(得分:0)

您似乎正在修补wrong place.

中的时区

假设您的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)