Attribute error when testing Flask with PyTest fixture - python-3.x

Can someone explain how I could use the pytest fixtures in my tests?
I have been given this conftest.py where it defines 3 pytest fixtures:
{...}
#pytest.fixture(scope='session', autouse=True)
def app(request):
app = create_app({
'TESTING': True
})
ctx = app.app_context()
ctx.push()
def teardown():
ctx.pop()
request.addfinalizer(teardown)
return app
#pytest.fixture(scope='function')
def client(app, request):
return app.test_client()
#pytest.fixture(scope='function')
def get(client):
return humanize_werkzeug_client(client.get)
I am trying to test my app using the above test fixtures. Based on my undersanding, I need to use that app fixture in my pytests. As seen in this blog, I have tried something like this:
def test_myflaskapp(app):
response = app.get('/')
assert response.status_code == 200
But I get an attribute error: AttributeError: 'Flask' object has no attribute 'get'. This answer did not make any sense to me I am afraid not sure if it even applies on my case.
Can someone explain how/what I am doing wrong? I am trying to learn Flask/PyTest and I cannot find an example/guide that explains how this works.

The app fixture returns a Flask instance, I think you want to use .get method on the test client instance. Try changing app to client in your test.
Check out the official docs on testing.

Related

Trying to test Flask endpoint. AttributeError: 'PosixPath' object has no attribute 'lstrip'

I'm trying to test my Flask API with pytest, but i'm strugling with this error: AttributeError: 'PosixPath' object has no attribute 'lstrip'. It's being raised when I do a post request to /authentication/users. The endpoint works perfectly when I use Postman and the unit tests im my User model works too (they don't use client). Here is my code:
# conftest.py
# ... imports here ...
#pytest.fixture
def client():
app.config.from_object(TestSettings)
with app.test_client() as client:
with app.app_context():
# starts connection with tests db
db = MongoEngine(app)
yield client # Do the tests
with app.app_context():
# drop db after tests
db.connection.drop_database('test')
My test that use client
# integrations/test_auth_endpoints.py
def test_create_user(client):
"""
GIVEN the /authentication/users endpoint
WHEN a POST request is made
THEN check the status code, message and data of response
"""
usr_data = {
"nome": "ana",
"cpf": "12345678913",
"email": "ana#gmail.com",
"password": "my-secret.#"
}
req = client.post('/authentication/users', data=usr_data) # <-- AttributeError here
assert req.status == 201
So what i'm doing wrong?
After a few hours of debugging. I figured out what I did wrong.
In my settings i have set
APPLICATION_ROOT = Path(__file__).parent.parent
I wrongly assumed APPLICATION_ROOT to be the project root and it was returning a PositionPath object.

Call Method of another class in Flask API

I am trying to expose a data service as an API for a PHP application. I have written the API logic in a file called APILogic.py. The code in this looks like this
class APILogic(object):
def __init__(self):
# the initialization is done here
...
def APIfunction(self):
# the API logic is built here, it uses the class variables and methods
...
I have created another file for the API purpose. Its called API.py. The code in this file looks like this
import APILogic from APILogic
class MyFlask:
def __init__(self):
self.test = APILogic()
from flask import Flask
app = Flask(__name__)
my_flask = MyFlask()
#app.route("/Test")
def Test():
return my_flask.test.APIfunction
if __name__ == "__main__":
app.run(debug=True,port=9999)
When I run the code, I get the error
> TypeError: APIfunction() takes 1 positional argument but 3 were given
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
There are no arguments for the APIfunction though.
Please help.
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
Looks like you're returning the method, but it sounds like you want to return the result of that method:
#app.route("/Test")
def Test():
return my_flask.test.APIfunction()
View function should return valid response.
Sample API code
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()

How to make put request with nested dictionaries to flask-restful?

The documentation example for a simple restful api is:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(host="0.0.0.0",port="80",debug=True)
However, suppose I made a put request with a nested dictionary,ie {'data':{'fruit':'orange'}}. The TodoSimple would have request.form.to_dict() = {'data':'fruit'}. How can I work with the full nested dictionary?
You should probably use Schemas to achieve this goal. Take a good look at this first example of marshmallow docs:
https://marshmallow.readthedocs.io/en/3.0/
As flask-restful docs says:
The whole request parser part of Flask-RESTful is slated for removal
and will be replaced by documentation on how to integrate with other
packages that do the input/output stuff better (such as marshmallow).

Unexpected AssertionError: single test not using logged in user from previous step

I am following the tutorial by http://www.patricksoftwareblog.com/flask-tutorial/, which I believe is based on https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world. Great stuff for a beginner.
I am getting different results when testing my code through frontend manually (which works fine) v.s. through pytest.
My test tries to show the "groups" endpoint which requires a login (standard #login_required decorator).
I initially test the user getting a login page ("Knock knock") when trying to get the endpoint without a login. This works manually and through pytest.
I login a user. If I inspect the response from the login I can clearly see a "Welcome back Pete!" success message.
My second assert receives a response from URL /login?next=%2Fgroups indicating the /groups endpoint is called without a login/authentication preceding it and the assert fails. Testing this manually works as expected. Why is that single test not using the same user/session combination in the next step(s)?
Test with the problem is the first snippet below:
def test_groups(app):
assert b'Knock knock' in get(app, "/groups").data
login(app, "pete#testmail.com", "pete123")
assert b'Test group 1' in get(app, "/groups").data
My "get" function for reference:
def get(app, endpoint: str):
return app.test_client().get(endpoint, follow_redirects=True)
My "login" function for reference:
def login(app, email="testuser#testmail.com", password="testing"):
return app.test_client().post('/login', data=dict(email=email, password=password), follow_redirects=True)
The app (from a conftest fixture imported in the test module by #pytest.mark.usefixtures('app')) for reference:
#pytest.fixture
def app():
"""An application for the tests."""
_app = create_app(DevConfig)
ctx = _app.test_request_context()
ctx.push()
yield _app
ctx.pop()
The login route for reference:
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST':
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.is_correct_password(form.password.data):
user.authenticated = True
user.last_login = user.current_login
user.current_login = datetime.now()
user.insert_user()
login_user(user)
flash(f'Welcome back {user.name}!', 'success')
return redirect(url_for('our_awesome_group.index'))
else:
flash('Incorrect credentials! Did you already register?', 'error')
else:
flash_errors(form)
return render_template('login.html', form=form)
The groups route for reference:
#app.route('/groups')
#login_required
def groups():
groups_and_users = dict()
my_group_uuids = Membership.list_groups_per_user(current_user)
my_groups = [Group.query.filter_by(uuid=group).first() for group in my_group_uuids]
for group in my_groups:
user_uuids_in_group = Membership.list_users_per_group(group)
users_in_group = [User.query.filter_by(uuid=user).first() for user in user_uuids_in_group]
groups_and_users[group] = users_in_group
return render_template('groups.html', groups_and_users=groups_and_users)
Im going to sum up the comments I made that gave the answer on how to solve this issue.
When creating a test app using Pytest and Flask there are a few different ways to go about it.
The suggested way to create a test client with proper app context is to use something like:
#pytest.fixture
def client():
""" Creates the app from testconfig, activates test client and context then makes the db and allows the test client
to be used """
app = create_app(TestConfig)
client = app.test_client()
ctx = app.app_context()
ctx.push()
db.create_all()
yield client
db.session.close()
db.drop_all()
ctx.pop()
That creates the client while pushing the app context so you can register things like your database and create the tables to the test client.
The second way is show in OP's question where use app.test_request context
#pytest.fixture
def app():
"""An application for the tests."""
_app = create_app(DevConfig)
ctx = _app.test_request_context()
ctx.push()
yield _app
ctx.pop()
and then create the test client in another pytest fixture
#pytest.fixture
def client(app):
return app.test_client()
Creating a test client allows you to use various testing features and gives access to flask requests with the proper app context.

How to create transactional fixture with peewee_async and pytest

I want to use transactions for tests with the pytest's fixtures as follows:
import pytest
from app.db.clients import (
get_database, # Returns instance of the class PooledMySQLDatabase
BaseManager # Extends the class peewee_async.Manager
)
db = get_database()
db.set_allow_sync(False)
objects = BaseManager(database=db)
#pytest.yield_fixture
async def transactional():
async with objects.transaction() as trx:
yield trx
await trx.rollback()
#pytest.mark.usefixtures('transactional')
#pytest.mark.asyncio:
async def test_transactional_fixture():
pass # Do something with the objects
However the code above doesn't work as expected in return tests are collected but aren't executing. Looks like pytest tries to yield tests infinite. I have no idea how to run transactional tests with such technology stack. Could someone help me, please?
P.S. The code snippet above is just represention of the project's workflow (screenshot was taken from real project).

Resources