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')
Related
I'm learning Flask and am a bit confused about how to structure my code. So I tried to extend Flask main class as follows:
from flask import Flask, ...
class App(Flask):
def __init__(self, import_name, *args, **kwargs):
super(App, self).__init__(import_name, *args, **kwargs)
Note that I am aware of that this may be a completely wrong approach.
So that when I want to start the app I do:
app = App(__name__)
if __name__ == '__main__':
app.run()
This way I can order my methods and routes in the class, but the problem is when using self-decorators:
#route('/')
def home(self, context=None):
context = context or dict()
return render_template('home.html', **context)
Which raises an error as unresolved reference 'route'. I guess this is not the way I should be structuring the app. How should I do it instead or how do I get the error fixed?
Doing this doesn't make sense. You would subclass Flask to change its internal behavior, not to define your routes as class methods.
Instead, you're looking for blueprints and the app factory pattern. Blueprints divide your views into groups without requiring an app, and the factory creates and sets up the app only when called.
my_app/users/__init__.py
from flask import Blueprint
bp = Blueprint('users', __name__, url_prefix='/users')
my_app/users/views.py
from flask import render_template
from my_app.users import bp
#bp.route('/')
def index():
return render_template('users/index.html')
my_app/__init__.py
def create_app():
app = Flask(__name__)
# set up the app here
# for example, register a blueprint
from my_app.users import bp
app.register_blueprint(bp)
return app
run.py
from my_app import create_app
app = create_app()
Run the dev server with:
FLASK_APP=run.py
FLASK_DEBUG=True
flask run
If you need access to the app in a view, use current_app, just like request gives access to the request in the view.
from flask import current_app
from itsdangerous import URLSafeSerializer
#bp.route('/token')
def token():
s = URLSafeSerializer(current_app.secret_key)
return s.dumps('secret')
If you really want to define routes as methods of a Flask subclass, you'll need to use self.add_url_rule in __init__ rather than decorating each route locally.
class MyFlask(Flask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_url_rule('/', view_func=self.index)
def index(self):
return render_template('index.html')
The reason route (and self) won't work is because it's an instance method, but you don't have an instance when you're defining the class.
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
Is there a way to access the view configuration from a renderer? By view configuration I mean the arguments passed to the view_config() decorator. My goal is to add some settings to the view configuration which a renderer can then use.
I have a custom renderer:
class MyRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
# Get view options.
my_renderer_opts = ...
# Render using options.
...
which is registered as:
config.add_renderer('my_renderer', MyRenderer)
Then in my view I have:
class Page(object):
def __init__(self, request):
self.request = request
#pyramid.view.view_config(
route_name='root',
renderer='my_renderer',
my_renderer_opts={...}
)
def view(self):
pass
Is there a way to access my_renderer_opts passed to view_config() from MyRenderer.__call__()?
if you still want implement it as described, maybe deriver will be helpfull:
from wsgiref.simple_server import make_server
from pyramid.view import view_config
from pyramid.config import Configurator
#view_config(route_name="hello", renderer="myrend", renderer_options={"a": 1})
def hello_world(request):
return "Hello World!"
def rendereropt_deriver(view, info):
options = info.options.get("renderer_options", {})
def wrapped(context, request):
setattr(request, "_renderer_options", options)
return view(context, request)
return wrapped
rendereropt_deriver.options = ("renderer_options",)
class MyRendererFactory:
def __init__(self, info):
self.info = info
def __call__(self, value, system):
options = getattr(system["request"], "_renderer_options", {})
print("`renderer_options` is {}".format(options))
return value
if __name__ == "__main__":
with Configurator() as config:
config.add_route("hello", "/")
config.add_view_deriver(rendereropt_deriver)
config.add_renderer("myrend", MyRendererFactory)
config.scan(".")
app = config.make_wsgi_app()
server = make_server("0.0.0.0", 8000, app)
server.serve_forever()
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
For every test I write that uses my apps models, I seem to have to use the current apps context:
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
...
test_something(self):
with self.app.app_context():
# Do something
Is there a way to tell all of my tests to run using the current apps context to save me having this line in all of my tests?
I found the answer I was looking for by looking at the way the Flask-Testing extensions TestCase sets itself up, i.e., pushing the testing context to the _ctx stack inside a function that's called from within it's __call__ method.
class BaseTestCase(unittest2.TestCase):
def __call__(self, result=None):
try:
self._pre_setup()
super(BaseTestCase, self).__call__(result)
finally:
self._post_teardown()
def _pre_setup(self):
self.app = create_app()
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
def _post_teardown(self):
if getattr(self, '_ctx') and self._ctx is not None:
self._ctx.pop()
del self._ctx
And my test:
class SomeTestCase(BaseTestCase):
test_something(self):
# Test something - we're using the right app context here
You can try something like below.
DISCLAIMER: I just came up with the idea and didn't test this solution thoroughly although it seems to work. It is also IMHO rather ugly.
from functools import wraps
def with_context(test):
#wraps(test)
def _with_context(self):
with self.app.app_context():
test(self)
return _with_context
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
...
#with_context
test_something(self):
# Do something
Depending on how you test you may be able to use the test client. Example:
SomeTestCase(unittest2.TestCase):
setUp(self):
self.app = Flask(__name__)
self.client = self.app.text_client()
test_something(self):
response = self.client.get('/something')
# check response