当使用 test_client 对 Flask RESTful API 进行单元测试时,等效于请求 Session object
Equivalent of a requests Session object when unit testing a Flask RESTful API using a test_client
基于我之前的一个问题(), I'm trying to test a Flask RESTful API using a test_client without the app running, rather than using requests 当应用程序 运行ning.
作为一个简单的例子,我有一个 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)
使用应用 运行ning,我 运行 这些单元测试(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
中的测试要求应用程序 运行ning,而 TestFlaskApi
中的测试则不需要。
我的问题是当使用 test_client
.这意味着如果我要编写更多测试,我将不得不将 headers
关键字参数单独传递给每个请求,这不是 DRY。
如何为 test_client
制作 'session'? (这似乎可以用 Werkzeug 的 EnvironBuilder 来完成,但我无法快速弄清楚如何做到这一点)。
为了在添加更多测试时保持代码干燥,我没有直接使用 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 decorating functions before call, How can I pass a variable in a decorator to function's argument in a decorated function?, and Flask and Werkzeug: Testing a post request with custom headers.
更新
可以使 authorized
包装器的定义更加简洁
from functools import partial
def authorized(function):
return partial(function, headers={'auth_token': AUTH_TOKEN})
基于我之前的一个问题(
作为一个简单的例子,我有一个 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)
使用应用 运行ning,我 运行 这些单元测试(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
中的测试要求应用程序 运行ning,而 TestFlaskApi
中的测试则不需要。
我的问题是当使用 test_client
.这意味着如果我要编写更多测试,我将不得不将 headers
关键字参数单独传递给每个请求,这不是 DRY。
如何为 test_client
制作 'session'? (这似乎可以用 Werkzeug 的 EnvironBuilder 来完成,但我无法快速弄清楚如何做到这一点)。
为了在添加更多测试时保持代码干燥,我没有直接使用 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 decorating functions before call, How can I pass a variable in a decorator to function's argument in a decorated function?, and Flask and Werkzeug: Testing a post request with custom headers.
更新
可以使authorized
包装器的定义更加简洁
from functools import partial
def authorized(function):
return partial(function, headers={'auth_token': AUTH_TOKEN})