我正在尝试为执行大量日期操作的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
生成的)...好奇,如果有办法可以避免这种情况。
答案 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)