Testing a POST that uses Flask-WTF validate_on_submit
Question:
I am stumped on testing a POST to add a category to the database where I’ve used Flask_WTF for validation and CSRF protection. For the CRUD operations pm my website. I’ve used Flask, Flask_WTF and Flask-SQLAlchemy. It is my first independent project, and I find myself a little at a lost on how to test the Flask-WTForm validate_on_submit function.
Here’s are the models:
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(250), unique=True)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(250), nullable=False, unique=True)
users_id = db.Column(db.Integer, db.ForeignKey('users.id'))
Here’s the form:
class CategoryForm(Form):
name = StringField(
'Name', [validators.Length(min=4, max=250, message="name problem")])
And here’s the controller:
@category.route('/category/add', methods=['GET', 'POST'])
@login_required
def addCategory():
""" Add a new category.
Returns: Redirect Home.
"""
# Initiate the form.
form = CategoryForm()
# On POST of a valid form, add the new category.
if form.validate_on_submit():
category = Category(
form.name.data, login_session['users_id'])
db.session.add(category)
db.session.commit()
flash('New Category %s Successfully Created' % category.name)
return redirect(url_for('category.showHome'))
else:
# Render the form to add the category.
return render_template('newCategory.html', form=form)
How do I write a test for the if statement with the validate_on_submit function?
Answers:
Using py.test and a conftest.py recommended by Delightful testing with pytest and SQLAlchemy, here’s a test that confirms the added category.
def test_add_category_post(app, session):
"""Does add category post a new category?"""
TESTEMAIL = "[email protected]"
TESTUSER = "Joe Test"
user = Users.query.filter(Users.email==TESTEMAIL).first()
category = Category(name="Added Category", users_id=user.id)
form = CategoryForm(formdata=None, obj=category)
with app.test_client() as c:
with c.session_transaction() as sess:
sess['email'] = TESTEMAIL
sess['username'] = TESTUSER
sess['users_id'] = user.id
response = c.post(
'/category/add', data=form.data, follow_redirects=True)
assert response.status_code == 200
added_category = Category.query.filter(
Category.name=="Added Category").first()
assert added_category
session.delete(added_category)
session.commit()
Note that the new category is assigned to a variable and then used to create a form. The form’s data is used in the post.
You should have different configurations for your app, depending if you are local / in production / executing unit tests. One configuration you can set is
WTF_CSRF_ENABLED = False
Working on the comments of @mas I got to this solution which worked for me:
topic_name = "test_topic"
response = fixt_client_logged_in.post('/create', data={"value":topic_name}, follow_redirects=True)
I am using this form class:
class SimpleSubmitForm(FlaskForm):
value = StringField(validators=[DataRequired()])
submit = SubmitField()
In this html file:
{{form.hidden_tag()}}
{{form.value.label("Topic", class="form-label")}}
{{form.value(value=topic_name, class="form-control")}}
<br/>
{{form.submit(value="submit", class="btn btn-primary")}}
Note that I am using the hidden_tag for the CSRF security, however when testing I have this extra line that de-activates it:
app.config['WTF_CSRF_ENABLED']=False
I have no idea how it actually works under the hood but my hypothesis is this: The wtform FlaskForm object looks at the "data" attribute of the request, which should be a dict. It then looks for keys in that dict that have the same name as its attributes. If it finds a key with the same name then it assigns that value to its attribute.
I am stumped on testing a POST to add a category to the database where I’ve used Flask_WTF for validation and CSRF protection. For the CRUD operations pm my website. I’ve used Flask, Flask_WTF and Flask-SQLAlchemy. It is my first independent project, and I find myself a little at a lost on how to test the Flask-WTForm validate_on_submit function.
Here’s are the models:
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(250), unique=True)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(250), nullable=False, unique=True)
users_id = db.Column(db.Integer, db.ForeignKey('users.id'))
Here’s the form:
class CategoryForm(Form):
name = StringField(
'Name', [validators.Length(min=4, max=250, message="name problem")])
And here’s the controller:
@category.route('/category/add', methods=['GET', 'POST'])
@login_required
def addCategory():
""" Add a new category.
Returns: Redirect Home.
"""
# Initiate the form.
form = CategoryForm()
# On POST of a valid form, add the new category.
if form.validate_on_submit():
category = Category(
form.name.data, login_session['users_id'])
db.session.add(category)
db.session.commit()
flash('New Category %s Successfully Created' % category.name)
return redirect(url_for('category.showHome'))
else:
# Render the form to add the category.
return render_template('newCategory.html', form=form)
How do I write a test for the if statement with the validate_on_submit function?
Using py.test and a conftest.py recommended by Delightful testing with pytest and SQLAlchemy, here’s a test that confirms the added category.
def test_add_category_post(app, session):
"""Does add category post a new category?"""
TESTEMAIL = "[email protected]"
TESTUSER = "Joe Test"
user = Users.query.filter(Users.email==TESTEMAIL).first()
category = Category(name="Added Category", users_id=user.id)
form = CategoryForm(formdata=None, obj=category)
with app.test_client() as c:
with c.session_transaction() as sess:
sess['email'] = TESTEMAIL
sess['username'] = TESTUSER
sess['users_id'] = user.id
response = c.post(
'/category/add', data=form.data, follow_redirects=True)
assert response.status_code == 200
added_category = Category.query.filter(
Category.name=="Added Category").first()
assert added_category
session.delete(added_category)
session.commit()
Note that the new category is assigned to a variable and then used to create a form. The form’s data is used in the post.
You should have different configurations for your app, depending if you are local / in production / executing unit tests. One configuration you can set is
WTF_CSRF_ENABLED = False
Working on the comments of @mas I got to this solution which worked for me:
topic_name = "test_topic"
response = fixt_client_logged_in.post('/create', data={"value":topic_name}, follow_redirects=True)
I am using this form class:
class SimpleSubmitForm(FlaskForm):
value = StringField(validators=[DataRequired()])
submit = SubmitField()
In this html file:
{{form.hidden_tag()}}
{{form.value.label("Topic", class="form-label")}}
{{form.value(value=topic_name, class="form-control")}}
<br/>
{{form.submit(value="submit", class="btn btn-primary")}}
Note that I am using the hidden_tag for the CSRF security, however when testing I have this extra line that de-activates it:
app.config['WTF_CSRF_ENABLED']=False
I have no idea how it actually works under the hood but my hypothesis is this: The wtform FlaskForm object looks at the "data" attribute of the request, which should be a dict. It then looks for keys in that dict that have the same name as its attributes. If it finds a key with the same name then it assigns that value to its attribute.