I am testing a flask app. I have many approutes that look something like this:
#bp.route('/place', methods=['GET'])
def get_json():
...
return json.dumps(some_data)
what I want to do is take this blueprint, instantiate it, and check that all methods that just dump json are dumping the json I am expecting within a test case. So far, I have this:
from blueprint.my_bp import my_bp
app = Flask(__name__)
app.register_blueprint(my_bp, url_prefix='/test')
my_bp.data = fake_data
def tests():
with app.test_client() as c:
for rule in app.url_map.iter_rules():
if len(rule.arguments) == 0 and 'GET' in rule.methods:
resp = c.get(rule.rule)
log.debug(resp)
log.debug(resp.data)
However, I have one method which looks something like this as it is unimplemented:
#bp.route('/')
def show_summary():
abort(404)
this will show up in my tests, as it seems it technically includes 'GET' in the methods.
Given this, is there any way to limit tests to only include ones which return a json?
One way could be to test if the response returns a JSON. You could try something like:
resp = c.get(rule.rule)
try:
json_data = resp.loads(resp.data) # this line will throw exception if not JSON
log.debug(resp)
log.debug(json_data)
except:
pass
Related
I try to save serialized BaseQuery object to the flask.session['t'] object but every time the get request is sent to this endpoint the session.get('t') is None.
q_transactions is non-empty list.
Could you help me to understand why it behaves that way? Did I miss or missunderstand something?
# app.py
#bp.route('/testing')
def testing():
import sys
from flask import session
from sqlalchemy.ext.serializer import loads, dumps
form = FiltersForm(request.args)
if session.get('t') is not None:
print('|'*80)
print(loads(session.get('t')), file=sys.stdout)
print('|'*80)
filters = form.data
q_transactions = current_user.transactions()
q_transactions = Transaction.apply_filters(q_transactions, filters)
session['t'] = dumps(q_transactions)
return render_template('test_edit_transaction.html', form=form)
EDIT:
The issue is probably caused by too large data.
I need to validate a Flask rule in a REST API that is built using Flask-Rebar. I've tried the following method:
#registry.handles(
rule='/horses/<int:horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
return {"horse_id": horse_id}
This is using the <int:my_var> syntax as specified here. When entering an integer value as the horse_id everything works correctly. The issue comes from entering a non-integer value, i.e. a; this throws a 404 Not Found status code when I was expecting a 400 Bad Request.
I don't think I can use any of the marshalling schemas for this so I'm unsure as to what to try next, any help is appreciated.
Thanks,
Adam
This is how Flask/Werkzeug behaves, so its a bit beyond Flask-Rebar's control. That is, the following will also return a 404 for /horses/a:
app = Flask(__name__)
#app.route('/horses/<int:horse_id>')
def getHorseById(horse_id):
return str(horse_id)
With that, here are some workarounds:
(1) Custom URL converter: http://flask.pocoo.org/docs/1.0/api/#flask.Flask.url_map
This would look something like:
import flask
from werkzeug.routing import BaseConverter
class StrictIntegerConverter(BaseConverter):
def to_python(self, value):
try:
return int(value)
except ValueError:
flask.abort(400)
app = flask.Flask(__name__)
app.url_map.converters['strict_integer'] = StrictIntegerConverter
#registry.handles(
rule='/horses/<strict_integer:horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
return {'horse_id': horse_id}
However, routing is done outside of application context, so we can't use flask.jsonify nor Flask-Rebar's errors to raise nice JSON errors.
(2) Check the type inside the handler function
from flask_rebar.errors import BadRequest
#registry.handles(
rule='/horses/<horse_id>',
method='GET',
marshal_schema=GetHorseByIdSchema()
)
def getHorseById(horse_id):
try:
horse_id = int(horse_id)
except ValueError:
raise BadRequest('horse_id must be an integer')
return {'horse_id': horse_id}
This is a little less elegant, but it works.
The Swagger document will default to a string type for the horse_id parameter, but we can work around that as well:
from werkzeug.routing import UnicodeConverter
class StrDocumentedAsIntConverter(UnicodeConverter):
pass
app.url_map.converters['str_documented_as_int'] = StrDocumentedAsIntConverter
registry.swagger_generator.register_flask_converter_to_swagger_type('str_documented_as_int', 'int')
#registry.handles(rule='/horses/<str_documented_as_int:horse_id>')
...
(3) Accept Flask/Werkzeug behavior
Depending on how badly you need 400 instead of 404, it might be most practical to do nothing and just give in to how Flask/Werkzeug do it.
I have bunch of test methods that i need to run and then after each test i want to update my results somewhere else.
This is what i have:
#pytest.mark.testcasename('1234')
#pytest.mark.parametrize('lang',
["EN", "FR"])
def test_text(self, request, base_url, lang):
testrail_conn = TestrailHelper()
test_case_id = request.node.get_marker("testcasename").args[0]
base_url = base_url.replace("testEN", "testFR") if lang == "FR" else base_url
self.navigate(base_url)
self.wait_for_page_loaded()
results = self.check_text(lang)
try:
assert results
testrail_conn.update_test_status(test_case_id, test_result=1)
except AssertionError:
testrail_conn.update_test_status(test_case_id, test_result=5)
My problem is that i want the update_test_status to be in a teardown fixture where i can pass my test_result to it. This way i dont need to write same code for each test method..
Any ideas?
Thanks
You could store something on the request object, e.g. on request.node - see the docs. Alternatively, you could use your fixture (as an argument) from the test, and store something there - or make the fixture return/yield some kind of data structure to store things in.
I'm trying to patch a public method for my flask application but it doesn't seem to work.
Here's my code in mrss.feed_burner
def get_feed(env=os.environ):
return 'something'
And this is how I use it
#app.route("/feed")
def feed():
mrss_feed = get_feed(env=os.environ)
response = make_response(mrss_feed)
response.headers["Content-Type"] = "application/xml"
return response
And this is my test which it's not parsing.
def test_feed(self):
with patch('mrss.feed_burner.get_feed', new=lambda: '<xml></xml>'):
response = self.app.get('/feed')
self.assertEquals('<xml></xml>', response.data)
I believe your problem is that you're not patching in the right namespace. See where_to_patch documentation for unittest.mock.patch.
Essentially, you're patching the definition of get_feed() in mrss.feed_burner but your view handler feed() already has a reference to the original mrss.feed_burner.get_feed(). To solve this problem, you need to patch the reference in your view file.
Based on your usage of get_feed in your view function, I assume you're importing get_feed like so
view_file.py
from mrss.feed_burner import get_feed
If so, you should be patching view_file.get_feed like so:
def test_feed(self):
with patch('view_file.get_feed', new=lambda: '<xml></xml>'):
...
I want to unit test my class, which is in another file named client_blogger.py.
My unit test file, is in the same directory. All of my other unit tests work, except when I try to mock one of my own methods.
## unit_test_client_blogger.py
import mock
import json
from client_blogger import BloggerClient, requests
Class TestProperties():
#pytest.fixture
def blog(self):
return BloggerClient(api_key='123', url='http://example.com')
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
And here is the client_blogger.py file contents:
# client_blogger.py
import requests, json
class BloggerClient(object):
""" Client interface for Blogger API. """
def __init__(self, key, url):
# removed some code here for brevity
def _send_request(self, api_request):
""" Sends an HTTP get request to Blogger API.
Returns HTTP response in text format. """
# snip
def _jload(self, api_response):
""" Accepts text API response. Returns JSON encoded response. """
# snip
def get_blog_info(self):
""" Makes an API request. Returns Blog item information. """
request = '{b}/blogs/byurl?url={u}&key={k}'.format(b=self.base, u=self.url, k=self.key)
txt_response = self.send_request(request)
response = self._jload(txt_response)
return response['id']
I want to mock out self.send_request() and self._jload() method calls in the above method.
But Mock module complains: ImportError: No module named client_blogger.
The error must lie here:
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
I've tried many variations in order to get mock.patch to find my module or class. But none of them have worked.
I've tried the following:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('BloggerClient._jload')
#mock.patch('._jload')
None of those work. Any idea how to mock.patch a method from my own module?
(It seems strange, because I can mock.patch other modules, just not my own :-s)
You want this:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('client_blogger.BloggerClient._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
The BloggerClient implementation is coming from the client_blogger module, so you need to patch client_blogger.BloggerClient. You list that as one of the things you tried that doesn't work, but I just tried it, and it works fine for me. What issue did you have when you tried that?
You need to include the file name of the class in the path, before the object name.
e.g. if I have a method named foo in a class named Event in tools/event.py the path will be as follows:
patch("tools.event.Event.foo", boo)
For python3, the format is as follows:
from unittest.mock import patch
#patch('client_blogger.BloggerClient._jload')
.
.
.
Docs: https://docs.python.org/3/library/unittest.mock.html#patch
This is very, very important:
patch() is straightforward to use. The key is to do the patching in the right namespace. See the section where to patch.