Skip to main content

How to Build a Contact Form for SME Systems with Flask CKEditor (Part 4)

pytest
In this tutorial, we’ll explore how to test a Flask application using pytest. We’ll use a simple contact form built with Flask-WTF and Flask-CKEditor to demonstrate testing form submissions and route responses. You’ll learn how to verify page redirects, form validation, and database inserts without affecting your real data. By the end, you’ll be able to write automated tests that make your Flask app more reliable and easier to maintain.

Preliminary
Before I begin, please activate the virtual environment and install the required dependencies.
python -m venv venv
venv\Scripts\activate
pip install pytest
Then, we need to set up the file and folder structure, as below:
test.py
All files and folders remain the same as in the previous tutorial, except I have created a test.py for the purpose of testing the Flask application. For the current tutorial, test.py is where we are running our testing of an application and an SQLite3 database.

Therefore, an isolated test will provide cleaner and more modular testing.

Before we dive into details, let us figure out:

⚙️ What is Pytest?

Pytest helps to check if code works correctly by running small test functions and verifying expected results.

🚀 Why use Pytest?

  • Simple syntax (assert statements)
  • No boilerplate code needed
  • Supports fixtures (like your Flask client)
  • Great for testing web apps, APIs, and databases

Step 1: Test the Flask application
The app fixture sets up the Flask app and test client, and I have included the code below:
import pytest
from app import app

# ----------------- Fixture ----------------- 
@pytest.fixture
def client():
    # Enable testing mode, so Flask behaves appropriately for tests
    app.config['TESTING'] = True
    
    # Disable CSRF protection for testing purposes
    # This allows us to submit forms without a CSRF token
    app.config['WTF_CSRF_ENABLED'] = False
     
    # Provide the test client
    with app.test_client() as client:
        # Provide the client to the test functions
        yield client
        
# ----------------- Test Routes -----------------
def test_index(client):
    # Test the home page (GET '/')
    response = client.get('/')
    
    # Assert that the response returns HTTP 200 (OK)
    assert response.status_code == 200
    
# ----------------- Test Form Submission -----------------
def test_submit_form(client):
    # Test submitting the contact form (POST '/message')
    response = client.post('/message', data={
        'name': 'Kelvin',
        'email': 'test@example.com',
        'subscribe': 'y',
        'message': 'Hello world'
    # Follow the redirect to the thank you page
    }, follow_redirects=True)
    
    # Assert that the page loaded successfully
    assert response.status_code == 200
    
    # Assert that the response contains the success message
    # This ensures that form validation passed and redirect worked
    assert b"sent successfully" in response.data.lower()
To run this test, first I need to enable testing mode and disable CSRF protection, as CSRF will block testing and cause the test to fail. Then I have to run 2 tests; one is a routes test, and the other is a submission test.

The routes test checks whether the application loads to contact.html; if it does, it passes the first test. 

The second test is whether Pytest can imitate the user adding data to the respective fields, such as entering "Kelvin" in the name field. Next, it will be redirected to the thankyou.html, and if it succeeds, then it will pass the test. In the terminal, I run the code below:

pytest flask_ckeditor/test.py

2 tests

If the above test passes, I can now conduct further tests on the SQLite3 database.


Step 2: Test the SQLite3 database
A database fixture is used to set up a temporary SQLite database and tables in app.py, as shown below.

In app.py, let me change the SQLite3 connection from
import sqlite3
conn = sqlite3.connect("flask_ckeditor/message.db")
to the code set up below
conn = sqlite3.connect(app.config.get('DATABASE', 'flask_ckeditor/message.db'))
Then, I go ahead to set up the SQLite3 database test.
import pytest
from app import app
import sqlite3

# ----------------- Fixture -----------------
@pytest.fixture
def client(tmp_path):
    # Enable testing mode, so Flask behaves appropriately for tests
    app.config['TESTING'] = True
    
    # Disable CSRF protection for testing purposes
    app.config['WTF_CSRF_ENABLED'] = False
    
    # Use a temporary SQLite database for testing
    test_db = tmp_path / "test_message.db"
    app.config['DATABASE'] = str(test_db)
    
    # Initialize database table for test
    conn = sqlite3.connect(app.config['DATABASE'])
    c = conn.cursor()
    c.execute("""
        CREATE TABLE message (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT NOT NULL,
            subscribe BOOLEAN,
            message TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()
    
    with app.test_client() as client:
        yield client
  
# ----------------- Test Database Insert -----------------
def test_db_insert(client):
    """
    Test that submitting the form actually inserts data into the database
    """
    # Submit form data
    client.post('/message', data={
        'name': 'Kelvin',
        'email': 'test@example.com',
        'subscribe': 'y',
        'message': 'Hello world'
    }, follow_redirects=True)
    
    # Connect to the temporary test database
    conn = sqlite3.connect(app.config['DATABASE'])
    c = conn.cursor()
    
    # Retrieve all rows from the 'message' table
    c.execute("SELECT * FROM message")
    result = c.fetchall()
    conn.close()
    
    # Assert that exactly one row was inserted
    assert len(result) == 1
    
    # Assert that the data matches what was submitted
    row = result[0]
    assert row[1] == 'Kelvin'                 # name
    assert row[2] == 'test@example.com'       # email
    assert row[3] == 1                        # subscribe (True stored as 1)
    assert row[4] == 'Hello world'            # message
As I mentioned earlier, I will set up a temporary database as "test_message.db" to avoid corruption with the actual data in message.db. Then I need to create a database schema as usual. Finally, I will create the test data and add it to "test_message.db." If the result is showing one (inserted). Then the test was passed.

As in the previous test, I will run the same command line again for the current test; the result is shown in the terminal below.
1 test
The terminal showed that all three tests had been passed.

Final wrap-up:
In this tutorial, we explored how to test a Flask application using pytest, focusing on form handling and database interactions. We learned how to simulate GET and POST requests, handle Flask-WTF forms, and address common issues like CSRF protection during testing. By using a separate test database, we ensured our tests remain safe and do not affect real data. With these techniques, you can now confidently build reliable, maintainable Flask applications backed by proper automated testing. 🚀

⬅ Previous Part                                                                                                               ➡ Next Part

Comments

Popular Posts

How to Design a Location Tracking Module for Desktop Business Systems Using Python?

This tutorial will show you how to create an interactive map application that allows users to input geographic coordinates and visualize locations on an interactive map using Python's Tkinter GUI framework enhanced with TTKBootstrap styling and the TkinterMapView widget.  Preliminary   Before I begin, it is recommended to activate the virtual environment before installing the relevant dependencies. python -m venv venv venv\Scripts\activate pip install ttkbootstrap tkintermapview As usual, we need to import the relevant module and set the root values from tkinter import * import ttkbootstrap as tb import tkintermapview from ttkbootstrap.dialogs import Messagebox root = tb.Window(themename='darkly') root.title('Find My Map') root.geometry("1200x1200") I would like to divide it into three sections.  a) Input Panel Layout,  The headers are mainly a label with a map emoji.  The main layout contains labels and input fields for latitude and longitude, ...

How to Build a Contact Form for SME Systems with Flask CKEditor (Part 3)

  In the previous tutorial, I used TinyDB as our storage solution to keep application data in a simple JSON-based format. While TinyDB is lightweight and easy to use, many web applications require a more structured and scalable database system. In this tutorial, we will switch to SQLite , a powerful relational database that integrates well with Python and web frameworks. By using SQLite, you will learn how to store, manage, and query data in a more structured way for real-world applications. Preliminary Before I begin, please activate the virtual environment and install the required dependencies. Since SQLite is part of the Python standard library, it is pre-installed. python -m venv venv venv\Scripts\activate pip install flask Then, we need to set up the file and folder structure, as below: Question 1: What is SQLite? SQLite is a lightweight, serverless relational database that stores data in a single file on your computer. Unlike traditional database systems, it does ...

How to build a Desktop Business System for Audiobook Workflow Management with Python?

In this tutorial, we will build a simple audiobook player using Python and CustomTkinter. You will learn how to convert text into speech using gTTS and play it with PyGame. We will also implement play, pause, and stop controls, like those in a real audio player. By the end, you will have a clean and functional desktop audiobook app. Preliminary   Before I begin, it is recommended to activate the virtual environment before installing the relevant dependencies. python -m venv venv venv\Scripts\activate pip install customtkinter pillow gTTS pygame CTkMessagebox pypdf Then, the following steps include setting up the file structure, app.py, and two additional folders: the uploads and media folders. The media folder contains the icons necessary to build the app; there are read, pause, and stop icons, as shown on the diagram. Step 1: Build up the app interface I have 4 sections here: the header, upload area, text body, and button group.  (a) The header—this section is basicall...