【问题标题】:How to mock <ModelClass>.query.filter_by() in Flask-SqlAlchemy如何在 Flask-SqlAlchemy 中模拟 <ModelClass>.query.filter_by()
【发布时间】:2017-07-17 20:18:35
【问题描述】:

简述

在Flask-SqlAlchemy中测试模型类时,如何mock方法.query.filter_by(),从而返回mocked模型对象列表?

详细信息

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

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

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

SomeModel.query.filter_by(...)

在我们的测试代码中,使用带有mockingPython unittest 模型,我们希望模拟filter_by() 调用,以便它返回我们设计的测试用例下的模型对象列表。

我们怎样才能做到这一点?

附言

我的谷歌搜索只找到this related post;虽然在课程开始时应用@patch("flask_sqlalchemy.SignallingSession", autospec=True) 对我不起作用。

我也尝试将函数模拟为如下代码 sn-p

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

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

RuntimeError: application not registered on db instance and no application bound to current context

来自 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

【问题讨论】:

  • 请将您的回溯发布为文本。屏幕截图不能被屏幕阅读器索引或阅读,我们也不能复制细节来检查给定部分的代码究竟是什么样的。
  • @MartijnPieters 谢谢。它使用 PasteBin 进行更新
  • 请不要粘贴链接。问题需要独立存在,请在此处发布回溯。
  • 跟踪@MartijnPieters 太长怎么样?
  • 在 Python 中,只有在遇到无限递归问题时才会发生这种情况。然后你可以剪掉重复的部分。

标签: python flask sqlalchemy mocking python-unittest


【解决方案1】:

您必须模拟整个映射器类;访问映射器上的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

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

替代方法是模拟 _QueryProperty.__get__ method(它支持 .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

演示:

>>> 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'>

【讨论】:

  • @NamGVU:您的模型的其余部分也将取决于 SQLAlchemy 属性,并且不属于被测系统的一部分。当您测试使用查询的代码是否有效时,您不是在测试模型是否有效(这主要是 SQLAlchemy 的测试域,而不是您的域)。在这种情况下,最好只模拟整个模型。
  • 今天@Martijn Pieters 还有一件事,我们什么时候通过return_value 进入内部属性,什么时候不去?
  • @NamGVU:您也可以使用() 调用,但不能将其用作分配目标。所以filter_by_mock = query_property_getter_mock().filter_by 有效,但filter_by_mock() = faked_result_set 无效。所以我个人更喜欢在任何地方使用.return_value 来保持代码对称,并帮助新手阅读我的代码和 Python 模拟库。
【解决方案2】:

只是来自Martijn Pieters answer的总结

目标

  • 我们想模拟.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)

代码 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)

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 2013-05-28
  • 1970-01-01
相关资源
最近更新 更多