Эквивалент объекта сеанса запросов при модульном тестировании Flask RESTful API с использованием test_client

Основываясь на моем предыдущем вопросе (Как выполнить модульное тестирование Flask RESTful API), я пытаюсь протестировать RESTful API Flask с помощью test_client без запущенного приложения вместо использования запросов во время работы приложения.

В качестве простого примера у меня есть API (flaskapi2.py) с функцией get, которая использует декоратор входа:

import flask
import flask_restful
from functools import wraps

app = flask.Flask(__name__)
api = flask_restful.Api(app)

AUTH_TOKEN = "foobar"

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if flask.request.headers.get("auth_token") == AUTH_TOKEN:
            return f(*args, **kwargs)
        else:
            return flask.abort(401)     # Return HTTP status code for 'Unauthorized'
    return decorated_function

class HelloWorld(flask_restful.Resource):
    @login_required
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == "__main__":
    app.run(debug=True)

Когда приложение запущено, я запускаю эти модульные тесты (test_flaskapi2.py в том же каталоге):

import unittest
import flaskapi2
import requests
import json

AUTH_TOKEN = "foobar"

class TestFlaskApiUsingRequests(unittest.TestCase):
    def setUp(self):
        self.session = requests.Session()
        self.session.headers.update({'auth_token': AUTH_TOKEN})

    def test_hello_world(self):
        response = self.session.get('http://localhost:5000')
        self.assertEqual(response.json(), {'hello': 'world'})

    def test_hello_world_does_not_work_without_login(self):
        response = requests.get('http://localhost:5000')        # Make an unauthorized GET request
        self.assertEqual(response.status_code, 401)             # The HTTP status code received should be 401 'Unauthorized'


class TestFlaskApi(unittest.TestCase):
    def setUp(self):
        self.app = flaskapi2.app.test_client()

    def test_hello_world(self):
        response = self.app.get('/', headers={'auth_token': AUTH_TOKEN})
        self.assertEqual(json.loads(response.get_data()), {'hello': 'world'})


if __name__ == "__main__":
    unittest.main()

Все тесты проходят. Обратите внимание, что тесты в TestFlaskApiUsingRequests требуют, чтобы приложение было запущено, а тесты в TestFlaskApi — нет.

Моя проблема в том, что я не смог найти эквивалент requests' Объект сеанса для "стандартизации" заголовков запроса при использовании test_client. Это означает, что если бы я написал больше тестов, мне пришлось бы передавать аргумент ключевого слова headers каждому запросу отдельно, что не является СУХИМ.

Как я могу создать сеанс для test_client? (Похоже, это можно сделать с помощью EnvironBuilder от Werkzeug. но я не смог быстро понять, как это сделать).


person Kurt Peek    schedule 18.01.2017    source источник


Ответы (1)


Чтобы сохранить код СУХИМ при добавлении дополнительных тестов, вместо прямого использования EnvironBuilder я написал декоратор authorized, который добавляет требуемый ключевой аргумент headers к любому вызову функции. Затем в тесте я вызываю authorized(self.app.get) вместо self.app.get:

def authorized(function):
    def wrap_function(*args, **kwargs):
        kwargs['headers'] = {'auth_token': AUTH_TOKEN}
        return function(*args, **kwargs)
    return wrap_function

class TestFlaskApi(unittest.TestCase):
    def setUp(self):
        self.app = flaskapi2.app.test_client()

    def test_hello_world(self):
        response = self.app.get('/', headers={'auth_token': AUTH_TOKEN})
        self.assertEqual(json.loads(response.get_data()), {'hello': 'world'})

    def test_hello_world_authorized(self):          # Same as the previous test but using a decorator
        response = authorized(self.app.get)('/')
        self.assertEqual(json.loads(response.get_data()), {'hello': 'world'})

Все тесты проходят как хотелось бы. Этот ответ был вдохновлен функциями оформления Python перед вызовом, Как я могу передать переменную в декоратор для аргумента функции в оформленной функции? и Flask и Werkzeug: тестирование почтового запроса с пользовательскими заголовками.

Обновить

Определение оболочки authorized можно сделать более кратким, используя functools.partial< /а>:

from functools import partial
def authorized(function):
    return partial(function, headers={'auth_token': AUTH_TOKEN})
person Kurt Peek    schedule 18.01.2017