如何在Flask-SqlAlchemy中模拟<ModelClass> .query.filter_by()

Nam*_* VU 11 python sqlalchemy mocking flask python-unittest

简单来说

在Flask-SqlAlchemy中测试模型类时,我们如何模拟该方法.query.filter_by()以返回模拟模型对象的列表?

全部细节

假设我们有一个模型类,如下面的代码

from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class SomeModel(db.Model):
    # more column mapping and methods go here
Run Code Online (Sandbox Code Playgroud)

然后在我们的Flask代码中调用

SomeModel.query.filter_by(...)
Run Code Online (Sandbox Code Playgroud)

在我们的测试代码,使用Python的单元测试模型与嘲讽,我们要嘲笑filter_by()调用,以便它在我们的设计测试案例返回模型对象的列表.

我们怎么做到这一点?

PS

我的谷歌搜索只发现了这个相关的帖子 ; 虽然@patch("flask_sqlalchemy.SignallingSession", autospec=True)在课程开始时申请不适合我.

我还尝试将函数模拟为下面的代码片段

@patch('app.model.some_model.SomeModel.query.filter_by')
def test_some_case(self, filterbyMOCK):
    # more test logic goes here
Run Code Online (Sandbox Code Playgroud)

并且代码在启动时立即出错

RuntimeError: application not registered on db instance and no application bound to current context
Run Code Online (Sandbox Code Playgroud)

来自PyCharm IDE的完整错误如下面的快照.

Traceback (most recent call last):
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1297, in patched
    arg = patching.__enter__()
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1353, in __enter__
    self.target = self.getter()
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1523, in <lambda>
    getter = lambda: _importer(target)
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1210, in _importer
    thing = _dot_lookup(thing, comp, import_path)
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/mock/mock.py", line 1197, in _dot_lookup
    return getattr(thing, comp)
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py", line 428, in __get__
    return type.query_class(mapper, session=self.sa.session())
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 78, in __call__
    return self.registry()
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/sqlalchemy/util/_collections.py", line 990, in __call__
    return self.registry.setdefault(key, self.createfunc())
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py", line 136, in __init__
    self.app = db.get_app()
  File "/home/namgivu/NN/code/someproject-cloud/venv/local/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py", line 809, in get_app
    raise RuntimeError('application not registered on db '
RuntimeError: application not registered on db instance and no application bound to current context
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 10

你必须模拟整个mapper类; 访问query映射器上的属性会导致会话加载:

@patch('app.model.some_model.SomeModel')
def test_some_case(self, some_model_mock):
    filter_by_mock = some_model_mock.query.filter_by
    # more test logic goes here
Run Code Online (Sandbox Code Playgroud)

那是因为该.query属性是一个描述符对象; 访问它会触发绑定到会话.

另一种方法是模拟_QueryProperty.__get__方法(支持.query属性); 如果必须使用实际SomeModel实例进行测试,则仅使用此选项

@patch('flask_sqlalchemy._QueryProperty.__get__')
def test_some_case(self, query_property_getter_mock):
    filter_by_mock = query_property_getter_mock.return_value.filter_by
    # more test logic goes here
Run Code Online (Sandbox Code Playgroud)

演示:

>>> from flask_sqlalchemy import SQLAlchemy
>>> db = SQLAlchemy()
>>> class SomeModel(db.Model):
...     id = db.Column(db.Integer, primary_key=True)
...
>>> from unittest import mock
>>> with mock.patch('__main__.SomeModel') as model_mock:
...     filter_by = model_mock.query.filter_by
...     SomeModel.query.filter_by(SomeModel.id == 'foo')
...
<MagicMock name='SomeModel.query.filter_by()' id='4438980312'>
>>> with mock.patch('flask_sqlalchemy._QueryProperty.__get__') as query_property_getter_mock:
...     filter_by_mock = query_property_getter_mock.return_value.filter_by
...     SomeModel.query.filter_by(SomeModel.id == 'foo')
...
<MagicMock name='__get__().filter_by()' id='4439035184'>
Run Code Online (Sandbox Code Playgroud)


Nam*_* VU 6

只是Martijn Pieters 回答的总结

目标

  • 我们想模拟.query.filter_by().all()结果,例如SomeModel.query.filter_by().all()

代码 01

@patch('flask_sqlalchemy._QueryProperty.__get__')
def test (
  self,
  queryMOCK,
):

  #setup
  queryMOCK\
    .return_value.filter_by\
    .return_value.all\
    .return_value = [1,22]

  #get actual
  modelObj = SomeModel.query.filter_by().all()
  print(modelObj)
Run Code Online (Sandbox Code Playgroud)

代码 02 - 与上面类似并使用 with

def test(self):
  with patch('flask_sqlalchemy._QueryProperty.__get__') as queryMOCK      #setup
    queryMOCK\
      .return_value.filter_by\
      .return_value.all\
      .return_value = [1,22]

    #get actual
    modelObj = SomeModel.query.filter_by().all()
    print(modelObj)
Run Code Online (Sandbox Code Playgroud)