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

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)

Related

Flask unit test throws SQLAlchemy DetachedInstanceError

This is my flask unit test setup, I launch an app_instance for all tests and rollback for each function to make sure the test DB is fresh and clean.
#fixture(scope="session", autouse=True)
def app_instance():
app = setup_test_app()
create_test_user_records()
return app
#commit_test_data
def create_test_user_records():
db.session.add_all([Test_1, Test_2, Test_3])
#fixture(scope="function", autouse=True)
def enforce_db_rollback_for_all_tests():
yield
db.session.rollback()
def commit_test_data(db_fn):
#functools.wraps(db_fn)
def wrapper():
db_fn()
db.session.commit()
return wrapper
They work quite well until one day I want to add an API test.
def test_admin(app_instance):
test_client = app_instance.test_client()
res = test_client.get("/admin")
# Assert
assert res.status_code == 200
The unit test itself worked fine and passed, however, it broke other unit tests and threw out error like
sqlalchemy.orm.exc.DetachedInstanceError: Instance <User at 0x115a78b90> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: https://sqlalche.me/e/14/bhk3)

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?

Automated testing in Flask

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'

DB changes made in fixture don't seem to persist to test

I'm writing some Pytest code using a sqlite db, to test some logic. I setup a root level fixture to instantiate a db engine:
class SqliteEngine:
def __init__(self):
self._conn_engine = create_engine("sqlite://")
self._conn_engine.execute("pragma foreign_keys=ON")
def get_engine(self):
return self._conn_engine
def get_session(self):
Session = sessionmaker(bind=self._conn_engine, autoflush=True)
return Session()
#pytest.fixture(scope="session")
def sqlite_engine():
sqlite_engine = SqliteEngine()
return sqlite_engine
Then in my test class, I have
class TestRbac:
#pytest.fixture(scope="class")
def setup_rbac_tables(self, sqlite_engine):
conn_engine = sqlite_engine.get_engine()
conn_engine.execute("attach ':memory:' as rbac")
Application.__table__.create(conn_engine)
Client.__table__.create(conn_engine)
Role.__table__.create(conn_engine)
session = sqlite_engine.get_session()
application = Application(id=1, name="test-application")
session.add(application)
session.flush()
client = Client(id=0, name="Test", email_pattern="")
session.add(client)
session.flush()
Finally in the test in that class, I tried
def test_query_config_data_default(self, sqlite_engine, setup_rbac_tables, rbac):
conn_engine = sqlite_engine.get_engine()
session = sqlite_engine.get_session()
client = Client(id=1, name=factory.Faker("name").generate(), email_pattern="")
session.add(client)
session.flush()
clients = sqlite_engine.get_session().query(Client).all()
for client in clients:
print(client.id, client.name)
However, only one client prints (and if I try for Application, none print), and I can't figure out why. Is this a problem with the fixture scopes? Or the engine? Or how sqlite works in pytest?
I'm not an expert on this but I think you need to define the fixture in such a way that the session is shared unless you plan to commit in each fixture. In setup_rbac_tables the session is destroyed with the function scope. And when get_session is called again a new session is created.
In my pytest sqlalchemy tests I do something like this, where the db fixture is a db session that is reused between fixtures and in the test:
#pytest.fixture
def customer_user(db):
from ..model.user import User
from ..model.auth import Group
group = db.query(Group).filter(
Group.name == 'customer').first()
if not group:
group = Group(name='customer', label='customer')
user = User(email=test_email_fmt.format(uuid4().hex), group=group)
db.add(user)
return user

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