unit-testing - 单元测试包含多个提交按钮的 Flask 表单

标签 unit-testing flask pytest werkzeug

我正在为 Flask 应用程序中的表单验证方法编写单元测试,该应用程序包含几个不同的提交按钮来控制逻辑流。

表单验证方法期望收到 ImmutibleMultiDict包含按钮名称和值的对象,如 ('btn', 'Save')('btn', 'Update')('btn', 'Delete') .不幸的是,我不知道如何在 pytest 中模拟或提供不同的按钮响应。

下面是来自表单验证方法的示例代码,根据提交中使用的按钮(“更新”或“保存”)具有一些不同的操作:

def validate(self):
    if request.form['btn'] == 'Update':
            if cn_continent_name and en_continent_name:
                flash('You have not made a change. There is nothing to update.', 'warning')
                return False
            if not _check_clean_chinese():
                return False

    if request.form['btn'] == 'Save':
            # check if Chinese name already exists in the DB
            if cn_continent_name:
                self.cn_name.errors.append("Chinese Continent Name already registered")
                return False
            # check the if English name already exists in the DB
            en_continent_name = ContinentsTable.query.filter_by(en_name=self.en_name.data).first()
            if en_continent_name:
                self.en_name.errors.append("English Country Name already registered")
                return False

下面的表单验证方法测试不起作用,因为缺少按钮名称-值信息来匹配被测试的表单验证逻辑,它希望检查 request.form['btn'] = 'Save' 的存在。或 request.form['btn'] = 'Update' .
class TestContinentsForm:
"""Continents form."""

def test_validate_continent_cn_name_already_registered(self, continent):
    """Enter Continent cn_name that is already registered."""
    form = ContinentsForm(cn_name=continent.cn_name, en_name='NewEngName')
    assert form.validate() is False
    assert 'Chinese Continent Name already registered' in form.cn_name.errors

下面是带有错误代码的测试失败,它出现错误的原因是因为验证需要一个 werkzeug ImmutibleMutltiDict 对象,该对象包含用于提交表单的按钮名称,但我没有正确提供按钮名称ImmutibleMultiDict 对象。

我已经尝试了很多东西,但在下面的测试中注释掉了一个例子 request.form.add('btn','Save')这不起作用,因为不能直接修改 ImmutibleMutliDict 对象:
self = <tests.test_forms.TestContinentsForm object at 0x10f8be908>
continent = Asia, 亚洲, yà zhōu!

def test_validate_continent_cn_name_already_registered(self, continent):
    """Enter Continent cn_name that is already registered."""
    form = ContinentsForm(cn_name=continent.cn_name, en_name='NewEngName')
    #request.form.add('btn','Save')
    #assert 'Save' in request.form
    >assert form.validate() is False

test_forms.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../aoscrdb_app/user/forms/locations/continents_form.py:70: in validate
if 'Delete' in request.form['btn']:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ImmutableMultiDict([]), key = 'btn'

def __getitem__(self, key):
    """Return the first data value for this key;
        raises KeyError if not found.

        :param key: The key to be looked up.
        :raise KeyError: if the key does not exist.
        """
    if key in self:
        return dict.__getitem__(self, key)[0]
    >raise exceptions.BadRequestKeyError(key)
   E werkzeug.exceptions.HTTPException.wrap.<locals>.newcls: 400: Bad Request

../venv/lib/python3.5/site-packages/werkzeug/datastructures.py:402: BadRequestKeyError

为了正确测试表单验证,ImmutableMultiDict 对象应该如下所示,包括 ('btn', 'Save')数据:
This is reqest.form =>ImmutableMultiDict([('cn_name', '中地'), ('btn', 'Save'), 
('en_name', 'Middle Earth'), 
('csrf_token', '1455956207##90932fcb2d1481be007f90e32040b6aba3e5fe68')])

我正在使用 pytest 和 factory-boy,下面是相关的 pytest fixture 和工厂。我已经尝试创建其他包含按钮数据的 pytest 固定装置,但这对我也不起作用:
@pytest.fixture()
def continent(db):
    """A continent for the tests."""
    continent = ContinentFactory()
    db.session.commit()
    return continent

class ContinentFactory(BaseFactory):
"""Continent factory."""
cn_name = '亚洲'
en_name = 'Asia'

class Meta:
    """Factory configuration."""
    model = ContinentsTable

我相信按钮应该存储在像 {'btn': 'Save'} 这样的字典中并可以访问测试框架,但我找不到最佳实现方式。谢谢!

最佳答案

如果你想测试你的 flask 逻辑(包括表单行为),Flask 已经有一个内置的方法,你可以注入(inject)你自己的 POST、GET 值:http://flask.pocoo.org/docs/0.10/testing/

但似乎您想要做的是专门测试表单的验证逻辑。在这种情况下,您需要做的是修改请求上下文并将您的按钮值注入(inject) request.form (基本上用您自己的替换 ImmutableMultiDict() )。这必须在请求上下文中完成。请参阅上面的链接。

下面是一些示例代码,展示了如何实现这一点:

表格

import wtforms
class SampleForm(wtforms.Form):
    btn = wtforms.fields.SubmitField('Cancel')

    def validate(self):
        if request.form['btn'] == 'Save':
            print('Saving...')
        elif request.form['btn'] == 'Update':
            print('Updating!')
        else:
            print('Some other btn action')

测试
from flask import Flask, request
from werkzeug import ImmutableMultiDict

def test_sample_form_validate():
    app = Flask(__name__)
    form = SampleForm()
    with app.test_request_context('/'):
        request.form = ImmutableMultiDict([('btn', 'Save')])
        form.validate() # Prints 'Saving...'
        request.form = ImmutableMultiDict([('btn', 'Update')])
        form.validate() # Prints 'Updating!'

运行 test_sample_form_validate函数应打印“正在保存...”,然后是“正在更新!”。当然,您需要将其余相关数据添加到 ImmutableMultiDict。

关于unit-testing - 单元测试包含多个提交按钮的 Flask 表单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35456771/

相关文章:

php - 在 PHPUnit 中测试表单输入

unit-testing - 无法从 intellij 中执行 Maven 项目中的单元测试

python - celery flask --- 错误 : [Errno 111] Connection refused

python-3.x - python : Mocking a local gitpython repository

python - 如何通过字符串访问 pytest fixture?

unit-testing - Mockito 使用不同的 Class 参数调用相同的方法

python - 发送消息时禁用登录 Flask-Mail

ruby-on-rails - 如何使用 Ruby 推送 Celery 任务

python - Pytest:模拟具有不同 side_effect 的相同方法的多次调用

unit-testing - 我应该什么时候设计和记录我的测试用例?