试图在Flask-SQLAlchemy中模拟模型的问题 [英] Problems trying to mock a Model within Flask-SQLAlchemy

查看:214
本文介绍了试图在Flask-SQLAlchemy中模拟模型的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Flask-SQLAlchemy测试一个包含SQLAlchemy模型的Flask应用程序,并且试图将一些模型模拟为某些接受某些模型作为参数的模型。

我试图做的玩具版本就是这样。假设我有一个模型给出:

  // file:database.py 
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
$ b $ class User(db.Model):
id = db.Column(db.Integer,primary_key = True)
username = db.Column(db.String(80),unique = True)
birthday = db.Column(db.Date)

这是导入到应用程序工厂模式构建的应用程序中:

  // file :app.py 
从烧瓶导入烧瓶
从数据库导入db

def create_app():
app = Flask(__ name__)
app.config ['SQLALCHEMY_DATABASE_URI'] ='sqlite:////tmp/test.db'
db.init_app(app)

以及一些需要 User 作为参数的函数:

  // file:actions.py 
import datetime

SECONDS_IN_A_YEAR = 31556926

def get_user_age(user):
return(datetime .date.today() - 用户.birthday).total_seconds()// SECONDS_IN_A_YEAR

此外,应该有一些意见和蓝图,在 app.py 中导入,并在应用程序中注册,后者在某处调用函数 get_user_age 。 b
$ b

我的问题是:我想测试函数 get_user_age ,而不必创建一个应用程序,注册一个假的数据库等,等等。这应该是没有必要的,这个功能完全独立于Flask应用程序中使用的事实。



所以我试过了:



pre $ import unittest

导入日期时间
导入模拟

从数据库导入用户
from actions import get_user_age
$ b $ class TestModels(unittest.TestCase):
def test_get_user_age(self):
user = mock.create_autospec(User,instance = True)
user.birthday = datetime.date(year = 1987,month = 12,day = 1)
print get_user_age(user)

这引起我一个 RuntimeError:应用程序未在数据库实例注册,没有应用程序绑定到当前上下文异常。所以我想是的,显然我必须修补一些对象,以防止它检查应用程序是否注册到数据库等。所以我尝试用 @ mock.patch(database.SQLAlchemy)和其他的东西装饰它。



<有没有人知道我应该修补什么来防止这种行为,或者即使我的测试策略都是错误的? 我在键盘上敲了几个小时之后,发现了一个解决方案。问题似乎如下(如果有人知道更好,请更正)。

当我运行 mock.create_autospec(User),模拟模块试图检查 User 的所有属性,为它将吐出的Mock对象创建适当的规范。发生这种情况时,它会尝试检查属性 User.query ,只有当您在Flask应用程序的范围内时才能评估它。



发生这种情况的原因是,当计算 User.query 时,会创建一个需要有效会话的对象。此会话由Flask-SQLAlchemy内的 SQLAlchemy 类中的 create_scope_session 方法创建。
$ b 这个方法实例化一个名为 SignallingSession 的类,它的 __ init __ 方法调用 SQLAlchemy.get_app 方法。这是在db中没有注册应用程序时引发 RuntimeError 的方法。

通过修补SignallingSession方法,一切正常。因为我不想与数据库进行交互,所以可以:

pre $ import $ unit
import datetime

从动作导入模拟

导入时间


@ mock.patch(flask_sqlalchemy.SignallingSession,autospec = True)
class TestModels(unittest.TestCase):

def test_age(self,session):
import database
$ b $ user = mock.create_autospec(database.User)
user.birthday = datetime.date(year = 1987,month = 12,day = 1)
print age(user)


I'm testing a Flask application that have some SQLAlchemy models using Flask-SQLAlchemy and I'm having some problems trying to mock a few models to some methods that receive some models as parameters.

A toy version of what I'm trying to do is like this. Suppose I have a model given by:

// file: database.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()  

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    birthday = db.Column(db.Date)

That is imported in an app that is built with the app factory pattern:

// file: app.py
from flask import Flask
from database import db

def create_app():
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
    db.init_app(app)

And some function that needs a User as parameter:

// file: actions.py
import datetime

SECONDS_IN_A_YEAR = 31556926

def get_user_age(user):
    return (datetime.date.today() - user.birthday).total_seconds() //  SECONDS_IN_A_YEAR

Moreover there should be a couple of views and blueprints that are imported in app.py and registered in the app that latter call the function get_user_age somewhere.

My problem is: I want to test the function get_user_age without having to create an app, registering with a fake database, etc, etc. That shouldn't be necessary, the function is totally independent from the fact that it is used in a Flask app.

So I tried:

import unittest

import datetime
import mock

from database import User
from actions import get_user_age

class TestModels(unittest.TestCase):
    def test_get_user_age(self):
        user = mock.create_autospec(User, instance=True)
        user.birthday = datetime.date(year=1987, month=12, day=1)
        print get_user_age(user)

That raises me a RuntimeError: application not registered on db instance and no application bound to current context exception. So I thought "yeah, obviously I must patch some object to prevent it from checking if the app is registered with the database and etc". So I tried decorating it with @mock.patch("database.SQLAlchemy") and other things to no avail.

Do anyone know what should I patch to prevent this behavior, or even if my test strategy is all wrong?

解决方案

So, I found a solution after banging my head on the keyboard for a few hours. The problem seems to be the following (if anyone knows better, please correct me).

When I run mock.create_autospec(User), the mock module tries to inspect all attributes of User to create the adequate spec for the Mock object it will spit out. When this happens, it tries to inspect the attribute User.query, which can only be evaluated when you are inside the scope of a Flask app.

This happens because when User.query is evaluated, an object is created that needs a valid session. This session is created by the create_scope_session method on the SQLAlchemy class inside Flask-SQLAlchemy.

This method instantiates a class called SignallingSession whose __init__ method calls the SQLAlchemy.get_app method. This is the method that raises the RuntimeError when there's no app registered in the db.

By patching the SignallingSession method everything works nicely. Since I don't want to interact with the database this is ok:

import unittest
import datetime

import mock

from actions import age


@mock.patch("flask_sqlalchemy.SignallingSession", autospec=True)
class TestModels(unittest.TestCase):

    def test_age(self, session):
        import database

        user = mock.create_autospec(database.User)
        user.birthday = datetime.date(year=1987, month=12, day=1)
        print age(user)

这篇关于试图在Flask-SQLAlchemy中模拟模型的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆