Reputation: 143
I have a python module security.py
which defines a decorator authorized()
.
I want to test the decorator. The decorator will receive a flask request header. The decorator is sth like this:
def authorized():
def _authorized(wrapped_func):
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
return _authorized
I want to mock the flask request header using a @patch
decorator.The test I wrote is sth like this:
@patch('security.request.headers', Mock(side_effect=lambda *args, **kwargs: MockHeaders({})))
def test_no_authorization_token_in_header(self):
@security.authorized()
def decorated_func(token='abc'):
return access_token
result = decorated_func()
self.assertEqual(result, None)
class MockHeaders(object):
def __init__(self, json_data):
self.json_data=json_data
but I always get the following error:
name = 'request'
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
How should I do it right?
Upvotes: 4
Views: 2400
Reputation: 1121266
Mock the whole request object to avoid triggering the context lookup:
@patch('security.request')
and build up the mock from there:
@patch('security.request')
def test_no_authorization_token_in_header(self, mock_request):
mock_request.headers= {}
@security.authorized()
def decorated_func(token='abc'):
return token
self.assertRaises(Abort):
result = decorated_func()
Since a missing token results in an Abort
exception being raised, you should explicitly test for that. Note that the request.headers
attribute is not called anywhere, so side_effect
or return_value
attributes don't apply here.
I ignored MockHeaders
altogether; your decorator is not using json_data
and your implementation is lacking a __contains__
method, so in
tests wouldn't work on that. A plain dictionary suffices for the current code-under-test.
Side note: authorized
is a decorator factory, but it doesn't take any parameters. It'd be clearer if you didn't use a factory there at all. You should also use functools.wraps()
to ensure that any metadata other decorators add are properly propagated:
from functools import wraps
def authorized(wrapped_func):
@wraps(wrapped_func)
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
then use the decorator directly (so no call):
@security.authorized
def decorated_func(token='abc'):
return access_token
Upvotes: 6