【问题标题】:Unable to retrieve database objects in Flask test case session无法在 Flask 测试用例会话中检索数据库对象
【发布时间】:2018-12-03 14:15:33
【问题描述】:

我正在测试使用 flask-restfulsqlalchemyflask-sqlalchemyfactory-boy 创建的 API。

我遇到了一个奇怪的问题,即在使用 factoryGET 请求之前创建的对象可用,但在 post/put 调用之前创建的对象不可用。

我已经建立了一个基于flask-testing的测试用例类:

# tests/common.py
from flask_testing import TestCase as FlaskTestCase

from app import create_app
from database import db
from config import TestConfig


class TestCase(FlaskTestCase):
    def create_app(self):
        return create_app(TestConfig)

    def setUp(self):
    db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

测试工厂:

# tests/factory.py

import factory

from database import db

from ..models import Item

class ItemFactory(factory.alchemy.SQLAlchemyModelFactory):
    class Meta:
        model = Item
        sqlalchemy_session = db.session

资源:

# resources.py
from flask import request
from flask_restful import Resource

from database import db
from .models import Item
from .serializers import ItemSerializer

class ItemResource(Resource):
    def get(self, symbol):
        obj = db.session(Item).filter(Item.symbol == symbol).first_or_404()
    return ItemSerializer(obj).data

    def put(self, symbol):
        params = request.get_json(silent=True)
        query = db.session(Item).filter(Item.symbol == symbol).update(params)

        db.session.commit()

        obj = db.session(Item).filter(Item.symbol == symbol).first_or_404()
        return ItemSerializer(obj).data

测试:

# tests/test_resources.py
import json

from tests.common import TestCase
from tests.factory import ItemFactory


def parse_response(response):
    return json.loads(response.get_data().decode())


class ResourcesTest(TestCase):
    def test_get_item(self):
        symbol = 'TEST'
        ItemFactory(symbol=symbol)

        response = self.client.get('/api/v1/items/%s' % symbol)
        results = parse_response(response)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(results['symbol'], symbol)

    def test_update_item(self):
        symbol = 'TEST'
        new_symbol = 'TEST_NEW'
        ItemFactory(symbol=symbol)

        response = self.client.put('/api/v1/items/%s' % symbol, json={'symbol': new_symbol})
        results = parse_response(response)

        self.assertEqual(response.status_code, 200)

test_update_item 中,我收到了 404。在我们输入self.client.put 之前,检查数据库显示新的Item 已创建。但是,当我们到达资源中的put 方法时,db.session(Item).query.all() 返回一个空数组。

flask-testing 文档中,我发现了这一段:

Another gotcha is that Flask-SQLAlchemy also removes the session instance at the end of every request (as should any thread safe application using SQLAlchemy with scoped_session). Therefore the session is cleared along with any objects added to it every time you call client.get() or another client method.

我认为问题在于会话的处理方式,但无法提出任何解决方案来确保我的测试通过。另一个有趣的观察是,对于self.client.putself.client.post,如果我将发布的数据更改为self.client.put('/some/url', data={'symbol': symbol}),则对象将在会话中可用。

【问题讨论】:

    标签: python unit-testing flask sqlalchemy factory-boy


    【解决方案1】:

    您可能需要查看 SQLAlchemy 会话处理。

    factory_boy 文档描述了几个选项:https://factoryboy.readthedocs.io/en/latest/orms.html#managing-sessions

    看起来工厂在 SQLAlchemy 会话中创建对象,但该会话并未写入数据库。 当您的代码进入烧瓶端时,该上下文可能正在使用与数据库的另一个连接(请参阅您引用的 flask-testing 文档),因此看不到尚未提交的对象。

    【讨论】:

      【解决方案2】:

      好的,找到了一个复杂的方法来解决这个问题。我认为调用request.get_json 会在其他地方实例化一个会话。解决方案是覆盖 json 数据的发布方式。

      tests/common.py:

      import json
      
      from flask_testing import TestCase as FlaskTestCase
      from flask.testing import FlaskClient
      
      from app import create_app
      from database import db
      from config import TestConfig
      
      
      class TestClient(FlaskClient):
          def open(self, *args, **kwargs):
              if 'json' in kwargs:
                  kwargs['data'] = json.dumps(kwargs.pop('json'))
                  kwargs['content_type'] = 'application/json'
              return super(TestClient, self).open(*args, **kwargs)
      
      
      class TestCase(FlaskTestCase):
          def create_app(self):
              app = create_app(TestConfig)
              app.test_client_class = TestClient
              return app
      
          def setUp(self):
              db.create_all()
      
          def tearDown(self):
              db.session.remove()
              db.drop_all()
      

      所以基本上我们将测试客户端中 post/put 调用中的任何 json kwargs 转换为数据,并将 content_type 设置为 json。在这里偶然发现了这个 (https://stackoverflow.com/a/40688088)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-03-13
        • 1970-01-01
        • 2021-09-20
        • 2012-12-18
        • 1970-01-01
        • 2019-02-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多