I have the following chunk of code
class APITests(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.app = app.test_client()
app.config['SECRET_KEY'] = 'kjhk'
def test_exn(self, query):
query.all.side_effect = ValueError('test')
rv = self.app.get('/exn/')
assert rv.status_code == 400
I want to check the return code of self.app.get('/exn/). However, I notice that query.all() propagates the exception to the test case as opposed to trapping it and returning an error code.
How do I check the return codes for invalid inputs when exceptions are thrown in Flask?
You would have to do the following to trap the exception.
class APITests(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.app = app.test_client()
app.config['SECRET_KEY'] = 'kjhk'
def test_exn(self, query):
query.all.side_effect = ValueError('test')
with self.assertRaises(ValueError):
rv = self.app.get('/exn/')
assert rv.status_code == 400
For the assertRaises to work, the function call must be contained within some sort of wrapper to catch the exception.
If it is just ran like you did, it is just executed on its own, the exception is not caught and the test case becomes an error instead.
By wrapping the function call within a “with statement”, the exception is passed back to the “assertRaises” in the clause. The “assertRaises” function can now do the comparison on the exception.
To read up more on this, check this link
You can assertRaise the exception from werkzeug.
Example:
from werkzeug import exceptions
def test_bad_request(self):
with self.app as c:
rv = c.get('/exn/',
follow_redirects=True)
self.assertRaises(exceptions.BadRequest)
Related
I am writing unit tests in python.
I am trying the following to make it a little more efficient.
caller
class TestTrait(TestCaseCommon):
APP_NAME = 'pytest'
HEADER = {'content-type': 'application/json'}
#mock.patch('dmp_graphql.utils.common_utils.get_test_user', return_value=TestCaseCommon.mock_get_role_check_test_user(role_type=AllUserRoleEnum.SUPERADMIN))
def test_get_all_categories_super_admin(self):
print(self)
implementation
class TestCaseCommon(TestCase):
def create_app(self):
return create_app(self.APP_NAME)
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def mock_get_role_check_test_user(role_type: AllUserRoleEnum, is_not_exist_user: bool = False,
account_id: int = 0):
if is_not_exist_user:
return None
if role_type == AllUserRoleEnum.SUPERADMIN:
query = db.session.query(User).filter(User.is_super_admin == True)
else:
...
pass
return user
The following error occurred.
During handling of the above exception, another exception occurred:
test_create_traits_uploader.py:7: in <module>
class TestTrait(TestCaseCommon):
test_create_traits_uploader.py:11: in TestTrait
#mock.patch('dmp_graphql.utils.common_utils.get_test_user', return_value=TestCaseCommon.mock_get_role_check_test_user(role_type=AllUserRoleEnum.SUPERADMIN))
../common.py:35: in mock_get_role_check_test_user
query = db.session.query(User).filter(User.is_super_admin == True)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/orm/scoping.py:163: in do
return getattr(self.registry(), name)(*args, **kwargs)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/util/_collections.py:1022: in __call__
return self.registry.setdefault(key, self.createfunc())
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/sqlalchemy/orm/session.py:3309: in __call__
return self.class_(**local_kw)
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py:136: in __init__
self.app = app = db.get_app()
/Users/lhs/.local/share/virtualenvs/hyper-dmp-v2-api-y1V5yWAL/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py:987: in get_app
raise RuntimeError(
E RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.
In the above situation, is there a way to share the app with the decorator?
Or I'd like to know if it's possible to implement it in another way.
The framework used is flask and the python version is 3.8.
try:
context.do_something()
except ValueError:
return False
I do i test this particular code. When i use side effort e.g.
context = mock.MagicMoc()
context.do_something.side_effect = ValueError
When i use pytest.raises, the test passes but the code is not tested.
I have tried using assert but it fails
Any suggestions
I'm assuming you're wrapping the try/except code in a function that you want to test. Here are two options to test this.
1) Use a context manager to check that an exception is raised, after changing your function to re-raise the ValueError (though if you're not going to do anything with it, you might as well just not catch it in the first place):
from unittest import TestCase, mock
def do_something(c):
try:
c.do_something()
except ValueError as e:
raise e
class TestSomething(TestCase):
def test_do_something(self):
context = mock.MagicMock()
context.do_something.side_effect = ValueError
with self.assertRaises(ValueError):
do_something(context)
2) Return True in the successful control path of your function, then just check this condition in your test:
from unittest import TestCase, mock
def do_something(c):
try:
c.do_something()
return True
except ValueError as e:
return False
class TestSomething(TestCase):
def test_do_something(self):
context = mock.MagicMock()
context.do_something.side_effect = ValueError
self.assertTrue(do_something(context))
I have the following endpoint,
#developer_blueprint.route("/init_db", methods=["POST"])
def initialize_database():
try:
upload_data(current_app)
logger.debug("Database entries upload.")
return jsonify({"result": "Database entries uploaded."}), 201
except Exception as e:
return jsonify({"error": str(e)})
def upload_data(app):
with open("src/core/data/data.json") as data_file:
data = json.load(data_file)
try:
current_app.db.put(("somenamespace", "test", "default"), data, None)
except Exception as e:
raise e
I'm trying to figure out how to unit test this (we need to get coverage on our code).
Do I just mock up app.db? How can I do that?
Any suggestions would be appreciated.
It is not uncommon to mock database calls for unit testing using something like unittest.mock and then run Aerospike in a container or VM for end-to-end testing.
However, keep in mind that the Aerospike Python client library is written in C for better performance and thus it is not easy to do partial patching (aka "monkey patching"). For example, you will get a TypeError: can't set attributes of built-in/extension type if you try to simply patch out aerospike.Client.put.
One approach is to create a mock client object to replace or sub-class the Aerospike client object. The implementation of this mock object depends on your code and the cases you are testing for.
Take the following example code in which app.db is an instance of the Aerospike client library:
# example.py
import aerospike
import json
class App(object):
db = None
def __init__(self):
config = {'hosts': [('127.0.0.1', 3000)]}
self.db = aerospike.client(config).connect()
def upload_data(app):
with open("data.json") as data_file:
data = json.load(data_file)
try:
app.db.put(("ns1", "test", "default"), data, None)
except Exception as e:
raise e
if __name__ == "__main__":
app = App()
upload_data(app)
In writing unit tests for the upload_data function let's assume you want to test for a success case which is determined to mean that the put method is called and no exceptions are raised:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
pass
def put(self, *args, **kwargs):
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
if __name__ == '__main__':
main()
In the test_upload_data_success method the App.db property is patched with the MockClient class instead of the aerospike.Client class. The put method of the MockClient instance is also patched so that it can be asserted that the put method gets called after upload_data is called.
To test that an exception raised by the Aerospike client is re-raised from the upload_data function, the MockClient class can be modified to raise an exception explicitly:
# test.py
from unittest import TestCase, main
from unittest.mock import PropertyMock, patch
from example import App, upload_data
from aerospike import Client, exception
class MockClient(Client):
def __init__(self, *args, **kwargs):
self.put_err = None
if 'put_err' in kwargs:
self.put_err = kwargs['put_err']
def put(self, *args, **kwargs):
if self.put_err:
raise self.put_err
else:
return 0
class ExampleTestCase(TestCase):
def test_upload_data_success(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = client = MockClient()
app = App()
with patch.object(client, 'put') as put_mock:
upload_data(app)
put_mock.assert_called()
def test_upload_data_error(self):
with patch.object(App, 'db', new_callable=PropertyMock) as db_mock:
db_mock.return_value = MockClient(put_err=exception.AerospikeError)
app = App()
with self.assertRaises(exception.AerospikeError):
upload_data(app)
if __name__ == '__main__':
main()
Here is my code:
import sys
class App(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
print sys.exc_info()
app = App()
with app:
try:
1/0
except:
print 'Caught you'
#sys.exc_clear()
and another example using flask app context:
from flask import Flask
app = Flask(__name__)
#app.teardown_appcontext
def teardown(exception):
print exception
with app.app_context():
try:
1 / 0
except:
pass
It's weird that if i didn't call sys.exc_clear when i handle exception, then when exiting app, sys.exc_info will still return exception info.
Is there any way to avoid this case?
In my project which is based on flask, i will rollback transaction when there is a exception. Although i handled exception, app context can still get it using sys.exc_info like code below showed:
#AppContext pop method
def pop(self, exc=None):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)
I cannot ask everyone in my team to call sys.exc_info when handling every single one exception.
How should i do to avoid this situation?
This is really a bug in how the AppContext handles your case.
The exception is automatically cleared the moment the current frame exits (so outside the with statement). If you called another function from within the with statement frame and handled the exception there, the sys.exc_info() information would be cleared again when that other function exits.
The AppContext.__exit__() method is correctly notified that you handled the exception and passes the exception value on to the AppContext.pop() method.
As such the Flask AppContext.pop() method should use a different sentinel value to detect that no exception was passed in; it could detect that None was passed in not as a default but as an actual value, indicating no exceptions were raised or were properly handled.
I've filed an issue with the project requesting that this is implemented, with accompanying pull request. This was merged and will be part of a future release of Flask.
You could use a monkeypatch to backport this to the current Flask version:
from flask import app, ctx
import sys
if ctx.AppContext.pop.__defaults__ == (None,):
# unpatched
_sentinel = object()
def do_teardown_appcontext(self, exc=_sentinel):
if exc is _sentinel:
exc = sys.exc_info()[1]
for func in reversed(self.teardown_appcontext_funcs):
func(exc)
app.appcontext_tearing_down.send(self, exc=exc)
app.Flask.do_teardown_appcontext = do_teardown_appcontext
def pop(self, exc=_sentinel):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = ctx._app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
ctx.appcontext_popped.send(self.app)
ctx.AppContext.pop = pop
I'm using the latest version of mock and python 2.7.3
I'm building my first flask app and I'm testing some basic middleware to see if flask.abort() happens (and when it does, I assert a method was called with the Unauthorized exception)
def test_invokes_raise_http_exception_when_apply_blows_up(self):
start_response = mock.Mock()
self.sut = BrokenMiddleware(self.app)
with mock.patch.object(self.sut, 'raise_http_exception') as raise_up:
self.sut.__call__({}, start_response)
raise_up.assert_called_once_with(Unauthorized(), start_response)
class BrokenMiddleware(Middleware):
def apply_middleware(self, environ):
flask.abort(401)
Here is my production code
class Middleware(object):
def __call__(self, environ, start_response):
try:
self.apply_middleware(environ)
except Exception as e:
return self.raise_http_exception(e, start_response)
def raise_http_exception(self, exception, start_response):
pass
The issue I'm having is that mock fails the assert because the 401 raised is not the same as the one I'm expecting in the assertion itself.
If I only care about the type, not the actual instance how could i rewrite the assertion?
You're probably not going to like it, but this is how I've done the same thing in the past:
self.assertIsInstance(raise_up.mock_calls[0][1][0], Unauthorized)
Here is some explanation
>>> print raise_up.mock_calls
[call(Unauthorized())]
>>> print raise_up.mock_calls[0]
call(Unauthorized())
>>> print raise_up.mock_calls[0][1]
(Unauthorized(),)
>>> print type(raise_up.mock_calls[0][1][0])
<type 'Unauthorized'>