Flask Application Factory: Isolate Tests and Configs
Replace a global Flask app with a factory function so each test gets its own instance and config.
11 Aug 2026, 12:48 UTC

The problem: one global app, shared test state
Many Flask tutorials start with a module-level object:
app = Flask(__name__)
That object is created at import time and imported by routes, extensions, and tests. The consequence is a single shared configuration and a single set of registered blueprints. If one test enables TESTING = True, the next test may inherit it. If a blueprint registers a URL prefix at import, every test sees that prefix even when it should not.
What the application factory changes
An application factory is a function that constructs and returns a new Flask instance. Instead of importing app, your code calls create_app() and receives a fresh object. Configuration is applied inside the function with app.config.from_object(), and extensions are bound to that specific instance.
Because the factory runs on demand, you can pass a different config class for development, testing, or production. Views should refer to current_app rather than a global app, which removes circular imports when blueprints are defined in separate modules.
Worked example: factory, config, and blueprint
Create a configuration module:
# app/config.py
class BaseConfig:
TESTING = False
URL_PREFIX = ""
class TestingConfig(BaseConfig):
TESTING = True
URL_PREFIX = "/test"
Define the factory:
# app/factory.py
import os
from flask import Flask, current_app
def create_app(config_object=None):
config_object = config_object or os.getenv("APP_CONFIG", "app.config.BaseConfig")
app = Flask(__name__)
app.config.from_object(config_object)
# Initialize extensions here, once per app instance.
# db.init_app(app)
from app.routes import main_bp
app.register_blueprint(main_bp, url_prefix=app.config.get("URL_PREFIX", ""))
return app
Register routes in a blueprint. Notice the view uses current_app, not a global app reference:
# app/routes.py
from flask import Blueprint, current_app, jsonify
main_bp = Blueprint("main", __name__)
@main_bp.get("/health")
def health():
return jsonify({
"status": "ok",
"prefix": current_app.config.get("URL_PREFIX", ""),
})
Run the factory from your project root with a virtualenv activated:
$ APP_CONFIG=app.config.TestingConfig python -c "from app.factory import create_app; app = create_app(); print(app.config['TESTING'])"
Expected check: the command prints True. No special permissions are required. Risk: if app.routes imports app from app.factory at module load, you will hit a circular import; keep the blueprint import inside the factory.
Verify isolation between tests
Each test can build its own client:
# tests/test_app.py
import pytest
from app.factory import create_app
@pytest.fixture
def client():
app = create_app("app.config.TestingConfig")
return app.test_client()
def test_health_uses_test_prefix(client):
response = client.get("/test/health")
assert response.status_code == 200
assert response.get_json()["prefix"] == "/test"
Run:
$ python -m pytest tests/test_app.py -v
Expected check: the test passes and the response prefix matches the testing config. To confirm isolation, add a second test that creates an app with BaseConfig and asserts /health is reachable without the /test prefix.
Limitations and practical check
- The factory adds indirection. New contributors must locate
create_app()instead of importingappdirectly. - Extensions initialized inside the factory are bound to one app instance. Repeated calls in a long-running process can waste resources if you discard instances without care.
- Flask behavior around app context and imports can vary by version. Check your installed version with
flask --versionand verify the factory against it before production use.
Practical check: start two clients from two different config objects and assert that current_app.config["TESTING"] differs between them. If both return the same value, the factory is leaking configuration.
Adopting the factory pattern does not change running infrastructure, so there is no rollback procedure. Treat it as a code refactor and verify with your test suite.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.