You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试时Flask上下文外无法使用SQLAlchemy Session求助

Flask单元测试中"Working outside of application context"问题解决

问题场景

Flask项目采用应用工厂模式,flaskapp/__init__.py创建应用实例,flaskapp/db.py基于current_app配置初始化SQLAlchemy的engine和Session,业务模块使用Session操作数据库正常。但执行unittest单元测试时,导入Session初始化测试数据库时触发错误:

RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.

关键代码

flaskapp/init.py

import os
from flask import Flask

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)

    app.config.from_mapping(
        SECRET_KEY=b'some_secret_key',
        DEBUG=True,
        SQLALCHEMY_DATABASE_URI=f'sqlite:///{os.path.join(app.instance_path, "flaskapp.sqlite")}',
    )

    if test_config is None:
        app.config.from_pyfile('config.py', silent=True)
    else:
        app.config.from_mapping(test_config)

    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    with app.app_context():
        from flaskapp.routes import home, auth

        from flaskapp.db import init_db
        init_db()

    return app

flaskapp/db.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from flask import current_app

engine = create_engine(current_app.config['SQLALCHEMY_DATABASE_URI'], echo=True)
Session = sessionmaker(bind=engine)

def init_db():
    import flaskapp.models as models
    models.Base.metadata.create_all(engine)

def drop_db():
    import flaskapp.models as models
    models.Base.metadata.drop_all(engine)

tests/init.py

import unittest

from flaskapp import create_app
from flaskapp.db import Session
from flaskapp.models import User

class BaseTestClass(unittest.TestCase):
    def setUp(self):
        self.app = create_app(test_config={
            'TESTING': True,
            'DEBUG': True,
            'APP_ENV': 'testing',
             # 传入测试数据库URI
            'SQLALCHEMY_DATABASE_URI': 'sqlite:///testdb.sqlite',
        })
        self.client = self.app.test_client()

        # 上下文
        with self.app.app_context():
            self.populate_db()

    def tearDown(self):
        pass

    def populate_db(self):
        with Session() as db_session:
            db_session.add(User(
                username='Harry',
                email='harry@yahoo.es',
                password = 'Harry123.'
            ))

            db_session.commit()

问题根源

flaskapp/db.py在模块加载阶段(即被import时)就直接执行create_engine(current_app.config['...']),此时Flask应用实例尚未创建,也没有激活应用上下文,current_app无法获取到有效应用对象,因此抛出错误。业务模块能正常运行是因为它们在应用上下文激活后才被导入(create_app的with app.app_context()块内),而测试代码在导入Session时,应用还未初始化,直接触发了模块级别的错误代码。

解决方案

1. 修改数据库初始化逻辑,延迟创建engine和Session

将flaskapp/db.py中的模块级别初始化改为通过函数触发,确保只有在应用上下文存在时才创建数据库连接:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 初始化为空,后续通过函数赋值
engine = None
Session = None

def init_engine_and_session(db_uri):
    """根据传入的数据库URI初始化engine和Session"""
    global engine, Session
    engine = create_engine(db_uri, echo=True)
    Session = sessionmaker(bind=engine)

def init_db():
    """创建数据库表"""
    import flaskapp.models as models
    models.Base.metadata.create_all(engine)

def drop_db():
    """删除数据库表"""
    import flaskapp.models as models
    models.Base.metadata.drop_all(engine)

2. 在应用工厂中触发数据库初始化

修改flaskapp/__init__.py,在创建应用并激活上下文后,调用init_engine_and_session初始化数据库连接:

import os
from flask import Flask

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)

    app.config.from_mapping(
        SECRET_KEY=b'some_secret_key',
        DEBUG=True,
        SQLALCHEMY_DATABASE_URI=f'sqlite:///{os.path.join(app.instance_path, "flaskapp.sqlite")}',
    )

    if test_config is None:
        app.config.from_pyfile('config.py', silent=True)
    else:
        app.config.from_mapping(test_config)

    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    with app.app_context():
        # 先初始化数据库连接
        from flaskapp.db import init_engine_and_session, init_db
        init_engine_and_session(app.config['SQLALCHEMY_DATABASE_URI'])
        init_db()

        # 再导入路由
        from flaskapp.routes import home, auth

    return app

3. 完善测试代码的清理逻辑

修改tests/__init__.py的tearDown方法,测试结束后清理测试数据库:

import unittest
import os

from flaskapp import create_app
from flaskapp.db import Session, drop_db
from flaskapp.models import User

class BaseTestClass(unittest.TestCase):
    def setUp(self):
        self.app = create_app(test_config={
            'TESTING': True,
            'DEBUG': True,
            'APP_ENV': 'testing',
            'SQLALCHEMY_DATABASE_URI': 'sqlite:///testdb.sqlite',
        })
        self.client = self.app.test_client()

        with self.app.app_context():
            self.populate_db()

    def tearDown(self):
        with self.app.app_context():
            drop_db()
        # 删除测试数据库文件
        if os.path.exists('testdb.sqlite'):
            os.remove('testdb.sqlite')

    def populate_db(self):
        with Session() as db_session:
            db_session.add(User(
                username='Harry',
                email='harry@yahoo.es',
                password='Harry123.'
            ))
            db_session.commit()

核心思路

避免在模块加载阶段依赖current_app这类需要应用上下文的对象,将数据库连接的初始化逻辑延迟到应用实例创建并激活上下文之后,通过应用工厂统一触发。这样无论是正常运行还是测试场景,都能保证数据库连接初始化时拥有有效的应用配置。

内容的提问来源于stack exchange,提问作者Chus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 10:50:25