如何最好地模拟Django用户?

3

我正在尝试通过 Mock 实例来实例化一个 User 实例。此 Mock 实例将被传递给另一个模型 Profile,在调用清理方法时,我会检查任何验证错误。

然而,我得到了这个错误:AttributeError: Mock object has no attribute '_state'

有一个先前的帖子:如何在 Django 中模拟用户和请求。不过,我想避免任何数据库调用。

有什么不同的方法可以让 Mock 在这种情况下正常工作吗?

#models.py

class Profile(models.Model):

    hobby = "Hobbyist"
    develop = "Developer"
    coding_level = (
        (hobby, hobby),
        (develop, develop)
    )

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )
    birth = models.DateField(verbose_name="Date Of Birth")
    coding_level = models.CharField(
        verbose_name="Experience",
        max_length=20,
        choices=coding_level, default=hobby, blank=False
    )
    bio = models.TextField(
        verbose_name="User Bio",
        validators=[MinValueValidator(10)]
    )
    github = models.URLField(
        verbose_name="GitHub link",
        validators=[check_submitted_link],
        unique=True
    )
    avatar = models.ImageField(upload_to="images/%Y/%m/%d/")

#test_models.py

class TestProfile__001(SimpleTestCase):

    def setUp(self):
        self.test_user = Mock(
            spec=User,
            username="test_username",
            email="test@email.com"
        )

        self.profile_data = {
            'user': self.test_user,
            'birth': '2019-10-07',
            'coding_level': 'hobbyist',
            'bio': "",
            'github': "http://www.test.com",
            'avatar': "image.txt"
        }

    def test_create_profile_fail(self):
        with self.assertRaises(ValidationError):
            test_profile = Profile(**self.profile_data)
            test_profile.clean_fields()
1个回答

0

我认为测试clean_fields没有意义,因为它已经是经过充分测试的Django代码库的一部分。但是如果您坚持要测试它,绝对不应该模拟User

让我们来看一下您尝试测试的代码(这是从clean_fields的摘录):

        raw_value = getattr(self, f.attname)
        if f.blank and raw_value in f.empty_values:
            continue
        try:
            setattr(self, f.attname, f.clean(raw_value, self))
        except ValidationError as e:
            errors[f.name] = e.error_list

我们看到它运行通过模型上的每个字段,尝试调用其 clean 方法 (source):

def clean(self, value):
    """
    Validate the given value and return its "cleaned" value as an
    appropriate Python object. Raise ValidationError for any errors.
    """
    value = self.to_python(value)
    self.validate(value)
    self.run_validators(value) 
    return value

OneToOneField 本身不引入任何这些方法,它是在类层次结构中更高的位置,在 ForeignKey 类中完成。这里是 validate 方法的主要部分

    using = router.db_for_read(self.remote_field.model, instance=model_instance)
    qs = self.remote_field.model._default_manager.using(using).filter(
        **{self.remote_field.field_name: value}
    )
    qs = qs.complex_filter(self.get_limit_choices_to())
    if not qs.exists():
        raise exceptions.ValidationError(
            self.error_messages['invalid'],
            code='invalid',
            params={
                'model': self.remote_field.model._meta.verbose_name, 'pk': value,
                'field': self.remote_field.field_name, 'value': value,
            },  # 'pk' is included for backwards compatibility
        )

正如您所看到的,整个验证过程仅包括构建查询并进行数据库调用!因此,如果您想避免访问数据库,则应完全跳过任何外键的验证:

def test_create_profile_fail(self):
    with self.assertRaises(ValidationError):
        test_profile = Profile(**self.profile_data)
        test_profile.clean_fields(exclude=['user'])  # don't forget to explain your decision in the comments

总之,模拟用户的最佳方法是在数据库中实际创建它。如果您想避免样板文件,可以使用factory_boy package


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接