Automated testing in Flask - python

I am having a problem with making automated tests for flask in python 3. I have tried unittest, pytests, nosetests but I still can't figure out how to form automated tests for flask application.
Following is the code I have wrote using unittest and pytest
unittest:
import unittest
from flaskfolder import flaskapp
class FlaskBookshelfTests(unittest.TestCase):
#classmethod
def setUpClass(cls):
pass
#classmethod
def tearDownClass(cls):
pass
def setUp(self):
# creates a test client
self.flaskapp = flaskapp.test_client()
# propagate the exceptions to the test client
self.flaskapp.testing = True
def tearDown(self):
pass
def test1(self):
result = self.flaskapp.get('/')
self.assertEqual(result.status_code, 200)
In this code i am having error that flaskapp doesn't has any test_client() function.
pytest:
import pytest
from flaskfolder import flaskapp
#pytest.fixture
def client():
db_fd, flaskapp.app.config['DATABASE'] = tempfile.mkstemp()
flaskapp.app.config['TESTING'] = True
with flaskapp.app.test_client() as client:
with flaskapp.app.app_context():
flaskapp.init_db()
yield client
os.close(db_fd)
os.unlink(flaskapp.app.config['DATABASE'])
def test1():
result = client.get('/')
assert result.data in 'Hello World'
In this error that "'function' doesn't has any attribute get" is recieved
and if done: def test1(client) it gives an error that flaskapp doesn't have any attribute init_db.

Because client is a pytest fixture, you need to include it as anargument to your test, so this should solve your current issue
def test1(client):
result = client.get('/')
assert result.data in 'Hello World'

Related

Using Pytest to monkeypatch an initialized redis connection in a flask application

I've been struggling with this for awhile now. I Have a flask app that is executed in my app.py file. In this file I have a bunch of endpoints that call different functions from other files. In another file, extensions.py, I've instantiated a class that contains a redis connection. See the file structure below.
#app.py
from flask import Flask
from extensions import redis_obj
app = Flask(__name__)
#app.route('/flush-cache', methods=['POST'])
def flush_redis():
result = redis_obj.flush_redis_cache()
return result
# extensions.py
from redis_class import CloudRedis
redis_obj = CloudRedis()
# redis_class
import redis
class CloudRedis:
def __init__(self):
self.conn = redis.Redis(connection_pool=redis.ConnectionPool.from_url('REDIS_URL',
ssl_cert_reqs=None))
def flush_redis_cache(self):
try:
self.conn.flushdb()
return 'OK'
except:
return 'redis flush failed'
I've been attempting to use monkeypatching in a test patch flush_redis_cache, so when I run flush_redis() the call to redis_obj.flush_redis_cache() will just return "Ok", since I've already tested the CloudRedis class in other pytests. However, no matter what I've tried I haven't been able to successfully patch this. This is what I have below.
from extensions import redis_obj
from app import app
#pytest.fixture()
def client():
yield app.test_client()
def test_flush_redis_when_redis_flushed(client, monkeypatch):
# setup
def get_mock_flush_redis_cache():
return 'OK'
monkeypatch.setattr(cloud_reids, 'flush_redis_cache', get_mock_flush_redis_cache)
cloud_redis.flush_redis = get_mock_flush_redis_cache
# act
res = client.post('/flush-cache')
result = flush_redis()
Does anyone have any ideas on how this can be done?

Why pytest does not catch exceptions?

I have a pytest test case like below:
#pytest.fixture
def app():
app = SampleApp().setup()
yield app
#pytest.fixture
def client(app):
return app.test_client()
def test_get_nonexistent_user(client, app):
with pytest.raises(NotFound) as err:
user = client.get('/users/5d4001f799556f10b7462e20')
assert err.type is NotFound
What I have read from pytest documentation I should be able to catch this exception. What I receive instead is as below:
1 failed in 0.25 seconds
And I see NotFound error in traceback error, so I'm sure code is raising the exception I expect it to.

AssertionError "assert not IOLoop.initialized()" on testing Tornado app with AsyncIOMainLoop

I have some trouble with writing tests with AsyncHTTPTestCase for existing Tornado application that uses asyncio event loop.
Here I prepare short model where I can reproduce issue:
app.py
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
AsyncIOMainLoop().install() # here is how to asyncio loop installed in app I already have
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
start.py
#!/usr/bin/env python3
import app
if __name__ == "__main__":
app.start_app()
test_app.py
import json
from tornado.testing import AsyncHTTPTestCase
import app
class TestHelloApp(AsyncHTTPTestCase):
def get_app(self):
return app.make_app()
def test_get(self):
response = self.fetch('/')
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '200 OK')
def test_post(self):
response = self.fetch('/', method="POST",
body=json.dumps({"key": "value"}))
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '201 OK')
With that approach of installation asyncio loop application works fine (I mean I can do requests and I'm getting responses), but test like this failed with error:
======================================================================
FAIL: test_post (test_app.TestHelloApp)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/testing.py", line 380, in setUp
self._app = self.get_app()
File "/home/biceps/work/torn/test_app.py", line 8, in get_app
return app.make_app()
File "/home/biceps/work/torn/app.py", line 14, in make_app
tornado.platform.asyncio.AsyncIOMainLoop().install()
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/ioloop.py", line 181, in install
assert not IOLoop.initialized()
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.006s
FAILED (failures=1)
Seems like loop that was installed by AsyncIOMainLoop().install() command is not stopped between tests, first test passed OK, but second always failed.
When I moved AsyncIOMainLoop().install() to start_app() method - tests are passed OK, but I'm worrying about that during test I use one event loop, but in real running app I use asyncio loop.
So, against that code tests are passed OK:
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
AsyncIOMainLoop().install()
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
Q: My question is - how to do write tests in that usecase correctly ? How to write tests with AsyncHTTPTestCase when Tornado app uses AsyncIOMainLoop ?
Am I right with decision to make AsyncIOMainLoop().install() into start_app(), not in make_app() function ?
P.S. I've added self.io_loop.clear_instance() to tearDown() - it looks probably dirty but that works for case when AsyncIOMainLoop().install() called from make_app() code.
def tearDown(self):
self.io_loop.clear_instance()
super().tearDown()
According to documentation I need to install AsyncIOMainLoop before startup application, not when I'm making app.
documentation
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
AsyncIOMainLoop().install()
asyncio.get_event_loop().run_forever()
So now I'm sure that proper way is using AsyncIOMainLoop installation into start_app() code.
So now my pattern code looks like:
web1.py
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
await asyncio.sleep(1)
return self.write("OK")
async def post(self, *args, **kwargs):
await asyncio.sleep(1)
return self.write("OK")
def make_app():
return tornado.web.Application([(r"/", MainHandler),],
debug=False)
def start_app():
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
AsyncIOMainLoop().install()
app = make_app()
app.listen(8888)
asyncio.get_event_loop().run_forever()
if __name__ == "__main__":
start_app()
test_app.py
from tornado.testing import AsyncHTTPTestCase
import web1
class TestTornadoAppBase(AsyncHTTPTestCase):
def get_app(self):
return web1.make_app()
def get_new_ioloop(self):
"""
Needed to make sure that I can also run asyncio based callbacks in my tests
"""
io_loop = tornado.platform.asyncio.AsyncIOLoop()
asyncio.set_event_loop(io_loop.asyncio_loop)
return io_loop
class TestGET(TestTornadoAppBase):
def test_root_get_method(self):
response = self.fetch("/")
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), 'OK')
def test_root_post_method(self):
response = self.fetch("/", method="POST", body="{}")
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), 'OK')
This pattern works as well, and during tests AsyncIOMainLoop is used. So I can use libraries those use asyncio loop. In my example there is asyncio.sleep() for example.

How to test that Flask is using test_client rather than client?

I'm using Pytest fixtures with Flask. My application is instantiated using an application factory.
#conftest.py
#pytest.fixture(scope='session')
def app(request):
'''Session-wide test application'''
app = create_app('testing')
app.client = app.test_client()
app_context = app.app_context()
app_context.push()
def teardown():
app_context.pop()
request.addfinalizer(teardown)
return app
I wanted to verify that the app created by my fixture uses Flask's built-in test_client, so I wrote a test:
#test_basics.py
def test_app_client_is_testing(app):
assert app.client() == app.test_client()
When I run this test, I get: TypeError: 'FlaskClient' object is not callable
What am I doing wrong?
Is the test incorrect, or is the fixture incorrect?
app.client is already an instance, you shouldn't call it again. Ultimately, this test makes no sense. Of course client is a test client, that's how you just created it in the fixture. Also, the clients will never be equal, they are different instances.
from flask.testing import FlaskClient
assert app.client == app.test_client() # different instances, never true
assert isinstance(app.client, app.test_client_class or FlaskClient) # still pointless, but correct
What you probably want is two fixtures: app and client, rather than creating a client on the app.
#pytest.yield_fixture
def app():
a = create_app('testing')
a.testing = True
with a.app_context():
yield a
#pytest.yield_fixture
def client(app):
with app.test_client() as c:
yield c
from flask.testing import FlaskClient
def test_app_client_is_client(app, client):
# why?
assert isinstance(client, app.test_client_class or FlaskClient)

Object Oriented Python with Flask Server?

I'm using Flask to expose some data-crunching code as a web service.
I'd like to have some class variables that my Flask functions can access.
Let me walk you through where I'm stuck:
from flask import Flask
app = Flask(__name__)
class MyServer:
def __init__(self):
globalData = json.load(filename)
#app.route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
if __name__ == "__main__":
app.run(host='0.0.0.0')
When I run getSomeData() outside of Flask, it works fine. But, when I run this with Flask, I get 500 internal server error. There's no magic here, and Flask has no idea that it's supposed to initialize a MyServer object. How can I feed an instance of MyServer to the app.run() command?
I could admit defeat and put globalData into a database instead. But, is there an other way?
You can create an instance of MyServer just outside the scope of your endpoints and access its attributes. This worked for me:
class MyServer:
def __init__(self):
self.globalData = "hello"
from flask import Flask
app = Flask(__name__)
my_server = MyServer()
#app.route("/getSomeData")
def getSomeData():
return my_server.globalData
if __name__ == "__main__":
app.run(host="0.0.0.0")
I know this is a late reply, but I came across this question while facing a similar issue. I found flask-classful really good.
You inherit your class from FlaskView and register the Flask app with your MyServer class
http://flask-classful.teracy.org/#
In this case, with flask-classful, your code would look like this:
from flask import Flask
from flask_classful import FlaskView, route
app = Flask(__name__)
class MyServer(FlaskView):
def __init__(self):
globalData = json.load(filename)
#route('/getSomeData')
def getSomeData():
return random.choice(globalData) #select some random data to return
MyServer.register(app, base_route="/")
if __name__ == "__main__":
app.run(host='0.0.0.0')
The least-coupled solution is to apply the routes at runtime (instead of at load time):
def init_app(flask_app, database_interface, filesystem_interface):
server = MyServer(database_interface, filesystem_interface)
flask_app.route('get_data', methods=['GET'])(server.get_data)
This is very testable--just invoke init_app() in your test code with the mocked/faked dependencies (database_interface and filesystem_interface) and a flask app that has been configured for testing (app.config["TESTING"]=True or something like that) and you're all-set to write tests that cover your entire application (including the flask routing).
The only downside is this isn't very "Flasky" (or so I've been told); the Flask idiom is to use #app.route(), which is applied at load time and is necessarily tightly coupled because dependencies are hard-coded into the implementation instead of injected into some constructor or factory method (and thus complicated to test).
The following code is a simple solution for OOP with Flask:
from flask import Flask, request
class Server:
def __init__(self, name):
self.app = Flask(name)
#self.app.route('/')
def __index():
return self.index()
#self.app.route('/hello')
def __hello():
return self.hello()
#self.app.route('/user_agent')
def __user_agent():
return self.user_agent()
#self.app.route('/factorial/<n>', methods=['GET'])
def __factorial(n):
return self.factorial(n)
def index(self):
return 'Index Page'
def hello(self):
return 'Hello, World'
def user_agent(self):
return request.headers.get('User-Agent')
def factorial(self, n):
n = int(n)
fact = 1
for num in range(2, n + 1):
fact = fact * num
return str(fact)
def run(self, host, port):
self.app.run(host=host, port=port)
def main():
server = Server(__name__)
server.run(host='0.0.0.0', port=5000)
if __name__ == '__main__':
main()
To test the code, browse the following urls:
http://localhost:5000/
http://localhost:5000/hello
http://localhost:5000/user_agent
http://localhost:5000/factorial/10
a bit late but heres a quick implementation that i use to register routes at init time
from flask import Flask,request,render_template
from functools import partial
registered_routes = {}
def register_route(route=None):
#simple decorator for class based views
def inner(fn):
registered_routes[route] = fn
return fn
return inner
class MyServer(Flask):
def __init__(self,*args,**kwargs):
if not args:
kwargs.setdefault('import_name',__name__)
Flask.__init__(self,*args ,**kwargs)
# register the routes from the decorator
for route,fn in registered_routes.items():
partial_fn = partial(fn,self)
partial_fn.__name__ = fn.__name__
self.route(route)(partial_fn)
#register_route("/")
def index(self):
return render_template("my_template.html")
if __name__ == "__main__":
MyServer(template_folder=os.path.dirname(__file__)).run(debug=True)
if you wish to approach MyServer class as a resource
I believe that flask_restful can help you:
from flask import Flask
from flask_restful import Resource, Api
import json
import numpy as np
app = Flask(__name__)
api = Api(app)
class MyServer(Resource):
def __init__(self):
self.globalData = json.load(filename)
def get(self):
return np.random.choice(self.globalData)
api.add_resource(MyServer, '/')
if __name__ == '__main__':
app.run()

Categories

Resources