How to fix decorator mock - python

I have a problem with mocking my decorator in my flask app.
I've tried monkeypatch, mock.patch and none is working
I'm running with python3.7
Here is my code
in => app/v1/views/user_data.py
#r.route('/myroute/<int:user_id>', methods=['GET'])
#verify_token
def ep_intern_midas_user_data_user_id(user_id):
# do some stuff here
return "Done"
in app/v1/decorators/verify_token.py
def verify_token(view_function):
#wraps(view_function)
def decorated_function(*args, **kwargs):
# do decorator stuff
return decorated_function
in tests/unit_tests/views/user_data.py
from functools import wraps
from app.v1 import decorators
def mock_decorator():
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
return 200
return decorated_function
return decorator
# !important thing - import of app after replace
decorators.verify_token = mock_decorator
from app import app
app.testing = True
def test_index():
with app.test_client() as client:
res = client.get('v1/intern/midas/user_data/1')
assert res.status_code == 200
(even with monkeypatch)
===== never works
I expected to have the result 200 but I have 401, what is the result if the decorator is called and not its mock.
Thanks for help

Related

Equivalent of a requests Session object when unit testing a Flask RESTful API using a test_client

Building on a previous question of mine (How to unit test a Flask RESTful API), I'm trying to test a Flask RESTful API using a test_client without the app running, rather than using requests while the app is running.
As a simple example, I have an API (flaskapi2.py) with a get function which uses a login decorator:
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)
With the app running, I run these unit tests (test_flaskapi2.py in the same directory):
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()
All the tests pass. Note that the tests in TestFlaskApiUsingRequests require the app to be running, whereas those in TestFlaskApi don't.
My problem is that I haven't been able to find the equivalent of requests' Session object to 'standardize' the request headers when using the test_client. This means that if I were to write more tests, I would have to pass the headers keyword argument to each request individually, which is not DRY.
How can I make a 'session' for the test_client? (It seems like this can be done with Werkzeug's EnvironBuilder but I wasn't able to quickly figure out how to do this).
In order to keep the code DRY when adding more tests, instead of using EnvironBuilder directly I wrote a decorator authorized which adds the required headers keyword argument to any function call. Then, in the test I call authorized(self.app.get) instead of 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'})
The tests all pass as desired. This answer was inspired by 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.
Update
The definition of the authorized wrapper can be made more succinct using functools.partial:
from functools import partial
def authorized(function):
return partial(function, headers={'auth_token': AUTH_TOKEN})

Python Decorators and Classes

I am working on a web server using Flask and Flask-Login. It was working until I needed to move it to a class. It is broken because of decorators: before I had the code
login_manager = LoginManager(app)
...
#login_required
def control_panel():
return render_template(...)
But this now becomes:
class webserver:
def __init__(self):
self.login_manager = LoginManager(app)
...
#login_required
def control_panel(self):
return self.render_template(...)
The issue is that I really need to write,
#self.login_required
But it is not valid syntax. How can I overcome this?
Thanks!
I'm not sure how your LoginManager creates the decorator, but you could make it work like this:
class LoginManager:
def __init__(self, app):
pass
#staticmethod
def login_required(func):
def inner(*args, **kwargs):
print "login_required: before"
func(*args, **kwargs)
print "login_required: after"
return inner
class Webserver:
def __init__(self):
self.login_manager = LoginManager(app)
#LoginManager.login_required
def control_panel(self):
print "control_panel: called"
app = {}
ws = Webserver()
ws.control_panel()
Output:
login_required: before
control_panel: called
login_required: after

Flask blueprint unit-testing

Is there a good practice to unit-test a flask blueprint?
http://flask.pocoo.org/docs/testing/
I didn't found something that helped me or that is simple enough.
// Edit
Here are my code:
# -*- coding: utf-8 -*-
import sys
import os
import unittest
import flask
sys.path = [os.path.abspath('')] + sys.path
from app import create_app
from views import bp
class SimplepagesTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('development.py')
self.test_client = self.app.test_client()
def tearDown(self):
pass
def test_show(self):
page = self.test_client.get('/')
assert '404 Not Found' not in page.data
if __name__ == '__main__':
unittest.main()
In this case, i test the blueprint. Not the entire app. To test the blueprint i've added the root path of the app to sys.path. Now i can import the create_app function to ...create the app. I also init the test_client.
I think i've found a good solution. Or will is there a better way?
I did the following if this helps anyone. I basically made the test file my Flask application
from flask import Flask
import unittest
app = Flask(__name__)
from blueprint_file import blueprint
app.register_blueprint(blueprint, url_prefix='')
class BluePrintTestCase(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
def test_health(self):
rv = self.app.get('/blueprint_path')
print rv.data
if __name__ == '__main__':
unittest.main()
Blueprints are very similar to application. I guess that you want test test_client requests.
If you want test blueprint as part of your application then look like no differences there are with application.
If you want test blueprint as extension then you can create test application with own blueprint and test it.
I have multiple APIs in one app and therefore multiple blueprints with url_prefix. I did not like that I have to prefix all paths when testing on of the APIs. I used the following class to wrap the test_client for blueprints:
class BlueprintClient():
def __init__(self, app_client, blueprint_url_prefix):
self.app_client = app_client
self.blueprint_url_prefix = blueprint_url_prefix.strip('/')
def _delegate(self, method, path, *args, **kwargs):
app_client_function = getattr(self.app_client, method)
prefixed_path = '/%s/%s' % (self.blueprint_url_prefix, path.lstrip('/'))
return app_client_function(prefixed_path, *args, **kwargs)
def get(self, *args, **kwargs):
return self._delegate('get', *args, **kwargs)
def post(self, *args, **kwargs):
return self._delegate('post', *args, **kwargs)
def put(self, *args, **kwargs):
return self._delegate('put', *args, **kwargs)
def delete(self, *args, **kwargs):
return self._delegate('delete', *args, **kwargs)
app_client = app.test_client()
api_client = BlueprintClient(app_client, '/api/v1')
api2_client = BlueprintClient(app_client, '/api/v2')

Unit-testing a flask-principal application

All, I'm writing a flask application that depends on flask-principal for managing user roles. I'd like to write some simple unit tests to check which views can be accessed by which user. An example of code is posted on pastebin to avoid cluttering this post. In short, I define a few routes, decorating some so that they can be accessed only by users with the proper role, then try to access them in a test.
In the code pasted, the test_member and test_admin_b both fail, complaining about a PermissionDenied. Obviously, I'm failing to declare the user properly; at least, the info about the user roles is not in the right context.
Any help or insight about the complexities of context processing will be deeply appreciated.
Flask-Principal does not store information for you between requests. It's up to you to do this however you like. Keep that in mind and think about your tests for a moment. You call the test_request_context method in the setUpClass method. This creates a new request context. You are also making test client calls with self.client.get(..) in your tests. These calls create additional request contexts that are not shared between each other. Thus, your calls to identity_changed.send(..) do not happen with the context of the requests that are checking for permissions. I've gone ahead and edited your code to make the tests pass in hopes that it will help you understand. Pay special attention to the before_request filter I added in the create_app method.
import hmac
import unittest
from functools import wraps
from hashlib import sha1
import flask
from flask.ext.principal import Principal, Permission, RoleNeed, Identity, \
identity_changed, identity_loaded current_app
def roles_required(*roles):
"""Decorator which specifies that a user must have all the specified roles.
Example::
#app.route('/dashboard')
#roles_required('admin', 'editor')
def dashboard():
return 'Dashboard'
The current user must have both the `admin` role and `editor` role in order
to view the page.
:param args: The required roles.
Source: https://github.com/mattupstate/flask-security/
"""
def wrapper(fn):
#wraps(fn)
def decorated_view(*args, **kwargs):
perms = [Permission(RoleNeed(role)) for role in roles]
for perm in perms:
if not perm.can():
# return _get_unauthorized_view()
flask.abort(403)
return fn(*args, **kwargs)
return decorated_view
return wrapper
def roles_accepted(*roles):
"""Decorator which specifies that a user must have at least one of the
specified roles. Example::
#app.route('/create_post')
#roles_accepted('editor', 'author')
def create_post():
return 'Create Post'
The current user must have either the `editor` role or `author` role in
order to view the page.
:param args: The possible roles.
"""
def wrapper(fn):
#wraps(fn)
def decorated_view(*args, **kwargs):
perm = Permission(*[RoleNeed(role) for role in roles])
if perm.can():
return fn(*args, **kwargs)
flask.abort(403)
return decorated_view
return wrapper
def _on_principal_init(sender, identity):
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret', TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
#app.before_request
def determine_identity():
# This is where you get your user authentication information. This can
# be done many ways. For instance, you can store user information in the
# session from previous login mechanism, or look for authentication
# details in HTTP headers, the querystring, etc...
identity_changed.send(current_app._get_current_object(), identity=Identity('admin'))
#app.route('/')
def index():
return "OK"
#app.route('/member')
#roles_accepted('admin', 'member')
def role_needed():
return "OK"
#app.route('/admin')
#roles_required('admin')
def connect_admin():
return "OK"
#app.route('/admin_b')
#admin_permission.require()
def connect_admin_alt():
return "OK"
return app
admin_permission = Permission(RoleNeed('admin'))
class WorkshopTest(unittest.TestCase):
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
def test_basic(self):
r = self.client.get('/')
self.assertEqual(r.data, "OK")
def test_member(self):
r = self.client.get('/member')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
def test_admin_b(self):
r = self.client.get('/admin_b')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
if __name__ == '__main__':
unittest.main()
As Matt explained, it's only a matter of context. Thanks to his explanations, I came with two different ways to switch identities during unit tests.
Before all, let's modify a bit the application creation:
def _on_principal_init(sender, identity):
"Sets the roles for the 'admin' and 'member' identities"
if identity.id:
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret',
TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
#
#app.route('/')
def index():
return "OK"
#
#app.route('/member')
#roles_accepted('admin', 'member')
def role_needed():
return "OK"
#
#app.route('/admin')
#roles_required('admin')
def connect_admin():
return "OK"
# Using `flask.ext.principal` `Permission.require`...
# ... instead of Matt's decorators
#app.route('/admin_alt')
#admin_permission.require()
def connect_admin_alt():
return "OK"
return app
A first possibility is to create a function that loads an identity before each request in our test. The easiest is to declare it in the setUpClass of the test suite after the app is created, using the app.before_request decorator:
class WorkshopTestOne(unittest.TestCase):
#
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
#app.before_request
def get_identity():
idname = flask.request.args.get('idname', '') or None
print "Notifying that we're using '%s'" % idname
identity_changed.send(current_app._get_current_object(),
identity=Identity(idname))
Then, the tests become:
def test_admin(self):
r = self.client.get('/admin')
self.assertEqual(r.status_code, 403)
#
r = self.client.get('/admin', query_string={'idname': "member"})
self.assertEqual(r.status_code, 403)
#
r = self.client.get('/admin', query_string={'idname': "admin"})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
#
def test_admin_alt(self):
try:
r = self.client.get('/admin_alt')
except flask.ext.principal.PermissionDenied:
pass
#
try:
r = self.client.get('/admin_alt', query_string={'idname': "member"})
except flask.ext.principal.PermissionDenied:
pass
#
try:
r = self.client.get('/admin_alt', query_string={'idname': "admin"})
except flask.ext.principal.PermissionDenied:
raise
self.assertEqual(r.data, "OK")
(Incidentally, the very last test shows that Matt's decorator are far easier to use....)
A second approach uses the test_request_context function with a with ... to create a temporary context. No need to define a function decorated by #app.before_request, just pass the route to test as argument of test_request_context, send the identity_changed signal in the context and use the .full_dispatch_request method
class WorkshopTestTwo(unittest.TestCase):
#
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
cls.testing = app.test_request_context
def test_admin(self):
with self.testing("/admin") as c:
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
#
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("member"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
#
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("admin"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
Along Matt's response, I've created a context manager to make the determine_identity a little cleaner:
#contextmanager
def identity_setter(app, user):
#app.before_request
def determine_identity():
#see http://stackoverflow.com/questions/16712321/unit-testing-a-flask-principal-application for details
identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
determine_identity.remove_after_identity_test = True
try:
yield
finally:
#if there are errors in the code under trest I need this to be run or the addition of the decorator could affect other tests
app.before_request_funcs = {None: [e for e in app.before_request_funcs[None] if not getattr(e,'remove_after_identity_test', False)]}
So when I run my test it looks like:
with identity_setter(self.app,user):
with user_set(self.app, user):
with self.app.test_client() as c:
response = c.get('/orders/' + order.public_key + '/review')
I hope this helps, and I would welcome any feedback :)
~Victor

Testing Python Decorators?

I'm writing some unit tests for a Django project, and I was wondering if its possible (or necessary?) to test some of the decorators that I wrote for it.
Here is an example of a decorator that I wrote:
class login_required(object):
def __init__(self, f):
self.f = f
def __call__(self, *args):
request = args[0]
if request.user and request.user.is_authenticated():
return self.f(*args)
return redirect('/login')
Simply:
from nose.tools import assert_equal
from mock import Mock
class TestLoginRequired(object):
def test_no_user(self):
func = Mock()
decorated_func = login_required(func)
request = prepare_request_without_user()
response = decorated_func(request)
assert not func.called
# assert response is redirect
def test_bad_user(self):
func = Mock()
decorated_func = login_required(func)
request = prepare_request_with_non_authenticated_user()
response = decorated_func(request)
assert not func.called
# assert response is redirect
def test_ok(self):
func = Mock(return_value='my response')
decorated_func = login_required(func)
request = prepare_request_with_ok_user()
response = decorated_func(request)
func.assert_called_with(request)
assert_equal(response, 'my response')
The mock library helps here.
A decorator like this might be tested simply thanks to duck-typing. Just supply a mock object to the call function, that seems to hold and act as a request, and see if you get the expected behaviour.
When it is necessary to use unit tests is quite individual i'd say. The example you give contain such basic code that one might say that it isn't necessary. But then again, the cost of testing a class like this is equally low.
Example for Django's UnitTest
class TestCaseExample(TestCase):
def test_decorator(self):
request = HttpRequest()
# Set the required properties of your request
function = lambda x: x
decorator = login_required(function)
response = decorator(request)
self.assertRedirects(response)
In general, the approach I've utilized is the following:
Set up your request.
Create a dummy function to allow the decorator magic to happen (lambda). This is where you can control the number of arguments that will eventually be passed into the decorator.
Conduct an assertion based on your decorator's response.
For those looking for a django type decorator test, this is how I ran tests on my custom django decorator:
common/decorators.py
from functools import wraps
from django.http import Http404
def condition_passes_test(test_func, fail_msg=''):
"""
Decorator for views that checks that a condition passes the given test,
raising a 404 if condition fails
"""
def decorator(view_func):
#wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if test_func():
return view_func(request, *args, **kwargs)
else:
raise Http404(fail_msg)
return _wrapped_view
return decorator
The test:
import django
from django.test import TestCase
from django.http import Http404
from django.http import HttpResponse
from django.test import RequestFactory
from common import decorators
class TestCommonDecorators(TestCase):
def shortDescription(self):
return None
def test_condition_passes_test1(self):
"""
Test case where we raise a 404 b/c test function returns False
"""
def func():
return False
#decorators.condition_passes_test(func)
def a_view(request):
return HttpResponse('a response for request {}'.format(request))
request = RequestFactory().get('/a/url')
with self.assertRaises(django.http.response.Http404):
a_view(request)
def test_condition_passes_test2(self):
"""
Test case where we get 200 b/c test function returns True
"""
def func():
return True
#decorators.condition_passes_test(func)
def a_view(request):
return HttpResponse('a response for request {}'.format(request))
request = RequestFactory().get('/app_health/z')
request = a_view(request)
self.assertEquals(request.status_code, 200)
NOTE: I Am using it as such on my view where my_test_function returns True or False:
#method_decorator(condition_passes_test(my_test_function, fail_msg="Container view disabled"), name='dispatch')

Categories

Resources