Невозможно получить объекты базы данных в сеансе теста Flask - PullRequest
0 голосов
/ 25 июня 2018

Я тестирую API, созданный с использованием flask-restful, sqlalchemy, flask-sqlalchemy и factory-boy.

Я столкнулся со странной проблемой, когда объекты, созданные до GET запросов с использованием factory, доступны, но объекты, созданные до вызовов 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.put или self.client.post, если я изменю данные, публикуемые как self.client.put('/some/url', data={'symbol': symbol}), объекты будут доступны в сеансе.

Ответы [ 2 ]

0 голосов
/ 26 июня 2018

Возможно, вам придется взглянуть на обработку сеанса SQLAlchemy.

Документация factory_boy описывает несколько опций: https://factoryboy.readthedocs.io/en/latest/orms.html#managing-sessions

Похоже, что фабрика создает объекты в сеансе SQLAlchemy, но этот сеанс не записывается в базу данных. Когда ваш код попадает в колбу, этот контекст может использовать другое соединение с базой данных (см. Цитированный вами документ flask-testing), таким образом, не видя объекты, которые еще не были зафиксированы.

0 голосов
/ 25 июня 2018

Хорошо, нашли запутанный способ решения этой проблемы. Я думаю, что вызов 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()

Таким образом, в основном мы конвертируем любые json kwargs при вызовах post / put в тестовом клиенте в данные, а также устанавливаем content_type в json. Наткнулся на это здесь (https://stackoverflow.com/a/40688088).

...