In my current Flask project, I wanted to test a view method that includes a form with a POST request. But it wouldn't work.
The view method looks something like this:
# views.py @tennis.route('/tournaments/add', methods=['GET', 'POST']) def add_tournament(): form = AddTournamentForm() if form.validate_on_submit(): t = Tournament(name=form.name.data) db.session.add(t) db.session.commit() flash('A tournament was added to the database.') return redirect(url_for('tennis.index')) return render_template('tennis/add_tournament.html', form=form)
Flask-Testing provides a
TestCase class with several useful assert methods. So the test would look like this:
# test_views.py from flask_testing import TestCase class TestTennisViews(TestCase): […] def test_tennis_add_tournament(self): # WHEN submitting correct form data to the add_tournament view r = self.client.post('/tennis/tournaments/add', data=dict( name='Chicharito Open', )) # THEN it redirects to the tennis overview page self.assertRedirects(r,'/tennis/') # and it shows a message self.assertMessageFlashed('A tournament was added to the database.') # and there is a tournament in the database assert Tournament.query.count() > 0 # and it has the correct tournament t = Tournament.query.first() assert t.name == 'Chicharito Open'
assertRedirects checks if the status code is
302. In my tests, it was always
200, and the test failed. Of course, nothing was added to the database, either.
I googled a lot, without success. I tried all combinations of the following keywords, among others:
flask unit test wtforms POST request testing
The solution hit me when taking a break (it works!). It lies in the
form.validate_on_submit() part of the view function.
validate_on_submit() does two things:
- It validates the form fields according to the validators specified in the form definition.
- It validates a CSRF token.
Guess what: there is no CSRF token generated when executing a POST request directly in the test.
The solution is easy, as
FLASK-WTF provides a configuration option to disable CSRF:
# config.py […] class TestingConfig(Config): TESTING = True WTF_CSRF_ENABLED = False […] class FunctionalTestingConfig(TestingConfig) WTF_CSRF_ENABLED = False
Of course, only disable CSRF in your test configuration! I created a separate configuration for my functional tests, so that CSRF is enabled in those. When using the browser with Selenium, the CSRF token is provided by the WTForms form on the web page.