如何在Django中对带验证码字段的表单进行单元测试?

17

我想通过提交表单来对Django视图进行单元测试。问题是,这个表单有一个验证码字段(基于django-simple-captcha)。

from django import forms
from captcha.fields import CaptchaField

class ContactForm(forms.forms.Form):
    """
    The information needed for being able to download
    """
    lastname = forms.CharField(max_length=30, label='Last name')
    firstname = forms.CharField(max_length=30, label='First name')
    ...
    captcha = CaptchaField()
测试代码:
class ContactFormTest(TestCase):

    def test_submitform(self):
        """Test that the contact page"""
        url = reverse('contact_form')

        form_data = {}
        form_data['firstname'] = 'Paul'
        form_data['lastname'] = 'Macca'
        form_data['captcha'] = '28if'

        response = self.client.post(url, form_data, follow=True)

有没有一种方法可以对这段代码进行单元测试,并在测试时去掉验证码?

提前感谢。


1
如果其他人像我一样来到这里,是在寻找有关django-recaptcha包的类似答案时偶然发现了这篇文章;结果他们也有一个设置。他们的文档描述了它的用法:https://github.com/praekelt/django-recaptcha - Dolan Antenucci
1
对于那些使用django-recaptcha并需要在单元测试中进行post的人,您还需要像这样发送“g-recaptcha-response”:self.client.post(url, {"g-recaptcha-response": "PASSED"}) - MrValdez
8个回答

22

我知道这篇文章有点旧了,但是django-simple-captcha现在有一个设置CAPTCHA_TEST_MODE,如果您提供值“PASSED”,则验证码会成功。 您只需要确保为两个验证码输入字段都发送一些内容:

post_data['captcha_0'] = 'dummy-value'
post_data['captcha_1'] = 'PASSED'
self.client.post(url, data=post_data)

CAPTCHA_TEST_MODE设置仅应在测试期间使用。我的settings.py:

if 'test' in sys.argv:
    CAPTCHA_TEST_MODE = True 

1
现在也可以使用from django.test import override_settings中的@override_settings(CAPTCHA_TEST_MODE=True);但不幸的是,截至2019年2月,存在一个问题,即此设置只读取一次 - 当应用程序启动时。请参见https://github.com/mbi/django-simple-captcha/issues/84。 - Jack L.

7

这是我解决这个问题的方法。导入实际保存验证码信息的模型:

from captcha.models import CaptchaStore

首先,我检查测试验证码表是否为空:

captcha_count = CaptchaStore.objects.count()
self.failUnlessEqual(captcha_count, 0)

在加载页面后(在这种情况下,是注册页面),请检查是否有新的验证码对象实例:
captcha_count = CaptchaStore.objects.count()
self.failUnlessEqual(captcha_count, 1)

然后,我获取验证码实例数据并将其与表单一起提交。在我的情况下,POST请求需要'captcha_0'包含哈希密钥,'captcha_1'包含响应。

captcha = CaptchaStore.objects.all()[0]
registration_data = { # other registration data here
                     'captcha_0': captcha.hashkey,
                     'captcha_1': captcha.response }

如果在运行此测试之前使用CaptchaStore实例,则可能需要稍微调整一下。希望这能有所帮助。


在注意到你的回答之前,我所做的方法是解析未绑定的HTML表单dom = PyQuery('<html><body>{}</body></html>'.format(f.as_p()),从中获取哈希值hashkey = dom('input[name="captcha_0"]').attr('value'),然后使用它查询数据库。其余部分基本相同。希望对某人有所帮助。 - alfetopito

4

我通过模拟ReCaptchaField进行了单元测试。首先,我在构造函数中添加了reCAPTCHA字段。它不能被作为普通字段添加,因为您无法对其进行模拟(一旦代码在应用模拟之前被评估):

class MyForm(forms.ModelForm):

    ...

    def __init__(self, *args, **kwargs):
        # Add captcha in the constructor to allow mock it
        self.fields["captcha"] = ReCaptchaField()

然后,我只需将ReCaptchaField替换为非必填的CharField即可。这样,我相信django-recaptcha会起作用。我只能测试自己的东西:

@mock.patch("trials.forms.ReCaptchaField", lambda: CharField(required=False))
def test_my_stuff(self):
    response = self.client.post(self.url, data_without_captcha)
    self.assert_my_response_fit_the_needs(response)

为了避免像我这样健忘的人需要查找它们,您需要: from django.db.models import CharFieldfrom unittest import mock - Phil Gyford

3
这是我们的做法。
@patch("captcha.fields.ReCaptchaField.validate")
def test_contact_view(self, validate_method):

    response = self.client.get(reverse("contact"))
    self.assertEqual(response.status_code, 200)

    data = {
        "name": "Bob Johnson",
        "email": "big_johnson@home.com",
        "phone": "800-212-2001",
        "subject": "I want Axis!",
        "message": "This is a giant\nThree liner..\nLove ya\n",
        "captcha": "XXX",
    }
    validate_method.return_value = True
    response = self.client.post(reverse("contact"), data=data)

    self.assertEqual(response.status_code, 302)

这种方法同样适用于使用ReCaptchaV3小部件的django-recaptcha包。 - Vitalii Mytenko

1

另一种解决方案与Jim McGaw的答案类似,但是不需要空表CaptchaStore表。

captcha = CaptchaStore.objects.get(hashkey=CaptchaStore.generate_key())

registration_data = { # other registration data here
                 'captcha_0': captcha.hashkey,
                 'captcha_1': captcha.response }

这将为该测试生成新的验证码。

1
一个解决方案是设置一个名为“测试”的选项,其值可以是真或假。然后只需
if not testing:
   # do captcha stuff here

这很简单易懂,而且切换也很容易。


它可以工作,但是必须在测试模块中导入表单之前设置 settings.UNIT_TEST = True。这就是我的错误原因。 - luc
1
你也可以在设置文件中设置测试:if“test”in sys.argv:TESTING = True - leech

1
这是唯一对我有效的方法, 在测试设置方法中将CAPTCHA_TEST_MODE设置为True。
class ApplicationTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.url = reverse('application')
        from captcha.conf import settings as captcha_settings
        captcha_settings.CAPTCHA_TEST_MODE = True
    
    def test_post_valid_form(self):
        data = {
            'name': 'John Doe',
            "captcha_0": "8e10ebf60c5f23fd6e6a9959853730cd69062a15",
            "captcha_1": "PASSED",
        }

        response = self.client.post(self.url, data)
        self.assertEqual(response.status_code, 200)

0

采用类似Jim McGaw的方法,但使用BeautifulSoup:

from captcha.models import CaptchaStore
from BeautifulSoup import BeautifulSoup

data = {...} #The data to post
soup = BeautifulSoup(self.client.get(url).content)
for field_name in ('captcha_0', ...): #get fields from the form
    data[field_name] = soup.find('input',{'name':field_name})['value']
captcha = CaptchaStore.objects.get(hashkey=data['captcha_0'])
data['captcha_1'] = captcha.challenge
response = self.client.post(url, data=data)

# check the results
...

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