Course Table of Contents
- Course Repository
- Course Introduction
- Part 1 - Docker, TimescaleDB, and Flask
- Part 2 - Dash
- Part 3 - Machine Learning
- Part 4 - Machine Learning Model in Dash
- Part 5 - Testing and Backups
- Part 6 - Deployment
It goes without saying on testdriven.io that app testing is extremely important. They say if it’s not tested, it’s already broken, and in my experience that’s absolutely true. At times in the past I’ve written what I thought was simple code, and deployed it to production without testing it, thinking surely it would work, only to be notified by a customer or a coworker that something was broken the next day… Learn from my mistakes and write tests for your code as you go.
Since our app is divided into Flask and Dash pages, we’ll start with testing the slightly simpler Flask pages in this chapter. Next chapter we’ll use Selenium WebDriver to perform front-end integration tests of the Dash/React single page application.
Flask Testing
For Python apps, there are two great choices for writing tests: the unittest module in the Python standard library, and pytest. I use both in different projects of mine, but for this course we’re going to use pytest.
Create a tests
directory next to your app
directory, and create four files in the directory:
- init.py - empty file signifying the
tests
directory is a Python package - conftest.py - for pytest options config for Dash (ignore for now)
- test_flask.py
- test_dash.py
Ignore the conftest.py
and test_dash.py
files for now. Those are for the next chapter on Dash testing.
In test_flask.py
add the following imports, and our first pytest “fixture”. Pytest fixtures are objects you can share among your various test functions. We’re going to be using the Flask app, and the Dash app, in our tests, so let’s create them once inside the flask_and_dash_tuple
fixture function.
Note also we’re disabling Flask-WTF CSRF protection, just in our tests. This way we can easily send POST requests to the Flask test server to login with our demo user.
import pytest
from app import create_app, db
from app.database import check_db_tables
from app.models import User
@pytest.fixture(scope="module")
def flask_and_dash_tuple():
"""
pytest fixture to share the Flask app
among different tests, if desired
"""
app, dashapp = create_app()
# Disable CSRF protection for the unit tests
app.config['WTF_CSRF_ENABLED'] = False
with app.app_context():
# This is where the testing happens
yield app, dashapp
Our next fixture won’t be used so much as run to ensure the database tables are available, and to add a demo user so we can login. We’re using the check_db_tables()
function to ensure the tables are setup.
Next we query the User
model (the public.users
database table) to see if the demo user has been added. If user
returns as None
(i.e. if not user
), we’ll create the user, and add and commit the user to the database so we can login.
@pytest.fixture(scope="module")
def init_database(flask_and_dash_tuple):
"""Initialize the testing database"""
flask_app, dashapp = flask_and_dash_tuple
# Create the database and the database tables if necessary
check_db_tables(flask_app, db)
# Insert a demo user so we can login
user = User.query.filter_by(email="demo@test.com").first()
if not user:
user = User(
email="demo@test.com",
password="password",
first_name="Demo",
last_name="User",
)
db.session.add(user)
# Commit the changes to the database
db.session.commit()
# This is where the testing happens
yield db
We’re finished with the pytest fixtures, so we can now create our first unit test–a simple one to start. The test starts with test_
so pytest can easily find it (all tests will start with test_
in this course), and the test function accepts our flask_and_dash_tuple
pytest fixture as an argument.
Inside the test function, we first extract the flask_app, dashapp
from the fixture, which is a tuple. We don’t need the dashapp
for these Flask tests, so feel free to change that variable name to _
to signify it’s not going to be used.
Flask comes with a built-in test_client
method for creating a test client, so we take advantage of that. We use the test_client
to send a GET request to our base route, and assert that the status_code
== 200 (successful request). We also check the response.data
to ensure it contains our simple text and link to the Dash page, in binary format.
def test_main_flask_route(flask_and_dash_tuple):
"""
Very simple Flask test to see if the main
Flask route is accessible
"""
flask_app, dashapp = flask_and_dash_tuple
test_client = flask_app.test_client()
# Send a GET request to the main Flask route
response = test_client.get("/")
# Test that it worked
assert response.status_code == 200
assert b'Click <a href="/dash/">here</a> to see the Dash single-page application (SPA)' in response.data
For our final Flask test, we are going to test the login and logout routes. Notice this test also uses the init_database
pytest fixture to ensure the demo user has been added to the database.
This time we’re submitting a POST request with our login credentials, instead of a basic GET request as in the previous test.
Once the demo user is logged in, she is redirected to the “/dash/” page, where we assert the “react-entry-point” ID is in the response data. All Dash pages have that ID in the HTML.
After the user is logged in, we can test the logout route, which redirects back to our simple homepage.
def test_login_and_logout(flask_and_dash_tuple, init_database):
"""
Given a Flask application,
when the "/login" page is posted to (POST),
check the response is valid
"""
flask_app, dashapp = flask_and_dash_tuple
test_client = flask_app.test_client()
response = test_client.post(
"/login/",
data=dict(
email="demo@test.com",
password="password"
),
follow_redirects=True
)
assert response.status_code == 200
# The 'react-entry-point' ID is in the Dash app, where we get redirected after login
assert b'id="react-entry-point"' in response.data
"""
Given a Flask application,
when the "/logout" page is requested (GET),
check the response is valid
"""
response = test_client.get("/logout/", follow_redirects=True)
assert response.status_code == 200
# We should be redirected back to the simple homepage
assert b'Click <a href="/dash/">here</a> to see the Dash single-page application (SPA)' in response.data
To run these tests, just run pytest
in your shell. Here are some optional arguments I like to add as well:
/workspace/tests/ # tests' location
-v # verbose Pytest logging
--lf # run the last-failed test first
-x # stop after first failed test
--headless # this makes Dash integration testing faster
That’s it for Flask testing. In the next chapter we’ll cover the more advanced Dash app testing with Selenium WebDriver.
Next: Dash Testing
Course Table of Contents
- Course Repository
- Course Introduction
- Part 1 - Docker, TimescaleDB, and Flask
- Part 2 - Dash
- Part 3 - Machine Learning
- Part 4 - Machine Learning Model in Dash
- Part 5 - Testing and Backups
- Part 6 - Deployment
Comments