How to pytest a Flask Endpoint - python

I'm getting started with Flask and Pytest in order to implemente a rest service with unit test, but i'm having some troouble.
I'll like to make a simple test for my simple endpoint but i keep getting a Working outside of application context. error when running the test.
This is the end point:
from flask import jsonify, request, Blueprint
STATUS_API = Blueprint('status_api', __name__)
def get_blueprint():
"""Return the blueprint for the main app module"""
return STATUS_API
#STATUS_API.route('/status', methods=['GET'])
def get_status():
return jsonify({
'status' : 'alive'
})
And this is how I'm trying to test it (i know it should fail the test):
import pytest
from routes import status_api
def test_get_status():
assert status_api.get_status() == ''
I'm guessing I just cant try the method with out building the whole app. But if that's the case i dont really know how to aproach this problem

The Flask documentation on testing is pretty good.
Instead of importing the view functions, you should create a so called test client, e.g. as a pytest fixture.
For my last Flask app this looked like:
#pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.app_context():
with app.test_client() as client:
yield client
(create_app is my app factory)
Then you can easily create tests as follows:
def test_status(client):
rv = client.get('/stats')
assert ...
As mentioned at the beginning, the official documentation is really good.

Have you considered trying an API client/development tool? Insomnia and Postman are popular ones. Using one may be able to resolve this for you.

Related

Running a single test works, but running multiple tests fails - Flask and Pytest

This is really strange. I have the following simple flask application:
- root
- myapp
- a route with /subscription_endpoint
- tests
- test_az.py
- test_bz.py
test_az.py and test_bz.py look both the same. There is a setup (taken from https://diegoquintanav.github.io/flask-contexts.html) and then one simple test:
import pytest
from myapp import create_app
import json
#pytest.fixture(scope='module')
def app(request):
from myapp import create_app
return create_app('testing')
#pytest.fixture(autouse=True)
def app_context(app):
"""Creates a flask app context"""
with app.app_context():
yield app
#pytest.fixture
def client(app_context):
return app_context.test_client(use_cookies=True)
def test_it(client):
sample_payload = {"test": "test"}
response = client.post("/subscription_endpoint", json=sample_payload)
assert response.status_code == 500
running pytest, will run both files, but test_az.py will succeed, while test_bz.py will fail. The http request will return a 404 error, meaning test_bz cannot find the route in the app.
If I run them individually, then they booth succeed. This is very strange! It seems like the first test is somehow influencing the second test.
I have added actually a third test test_cz.py, which will fail as well. So only the first one will ever run. I feel like this has something todo with those fixtures, but no idea where to look.
Create a conftest.py for fixtures e.g. for client fixture and use the same fixture in both tests?
Now if you're saying that the provided code is the example of a test that is the same in another file, then you are creating 2 fixtures for a client. I would first clean it up and create a 1 conftest.py that contains all the fixtures and then use them in your tests this might help you.
Check out also how to use pytest as described in Flask documentation

Application context errors when locally testing a (Python) Google Cloud Function

I am trying to locally test a Python function that I hope to deploy as a Google Cloud Function. These functions seem to be essentially Flask based, and I have found that the best way to return JSON is to use Flask's jsonify function. This seems to work fine when deployed, but I want to set up some local unit tests, and here is where I got stuck. Simply adding the line to import jsonify, results in the following error:
RuntimeError: Working outside of application context.
There are several posts here on Stackoverflow that seem relevant to this issue, and yet Google Cloud Functions do not really follow the Flask pattern. There is no app context, as far as I can tell, and there are no decorators. All of the examples I've found have not been useful to this particular use case. Can anyone suggest a method for constructing a unit test that will respect the application context and still jibe with the GCF pattern here.
I have a unittest, which I can share, but you will see the same error when you run the following, with the method invocation inside of main.
import os
import json
from flask import jsonify
from unittest.mock import Mock
def dummy_request(request):
request_json = request.get_json()
if request_json and 'document' in request_json:
document = request_json['document']
else:
raise ValueError("JSON is invalid, or missing a 'docuemnt' property")
data = document
return jsonify(data)
if __name__ == '__main__':
data = {"document":"This is a test document"}
request = Mock(get_json=Mock(return_value=data), args=data)
result = dummy_request(request)
print(result)
You don't really need to test whether flask.jsonify works as expected, right? It's a third-party function.
What you're actually trying to test is that flask.jsonify was called with the right data, so instead you can just patch flask.jsonify, and make assertions on whether the mock was called:
import flask
from unittest.mock import Mock, patch
def dummy_request(request):
request_json = request.get_json()
if request_json and 'document' in request_json:
document = request_json['document']
else:
raise ValueError("JSON is invalid, or missing a 'docuemnt' property")
data = document
return flask.jsonify(data)
#patch('flask.jsonify')
def test(mock_jsonify):
data = {"document": "This is a test document"}
request = Mock(get_json=Mock(return_value=data), args=data)
dummy_request(request)
mock_jsonify.assert_called_once_with("This is a test document")
if __name__ == '__main__':
test()
I'd recommend you to take a look at Flask's documentation on how to test Flask apps, it's described pretty well how to setup a test and get an application context.
P.S. jsonify requires application context, but json.dumps is not. Maybe you can use the latter?
I came across the same issue. As you've said the flask testing doesn't seem to fit well with Cloud Functions and I was happy with how the code worked so didn't want to change that. Adding an application context in setUp() of testing then using it for the required calls worked for me. Something like this...
import unittest
import main
from flask import Flask
class TestSomething(unittest.TestCase):
def setUp(self):
self.app = Flask(__name__)
def test_something(self):
with self.app.app_context():
(body, code) = main.request_something()
self.assertEqual(200, code, "The request did not return a successful response")
if __name__ == '__main__':
unittest.main()

Test Flask Dance with unittest

I have written a flask application that uses flask dance for user authentication. Now I want to test a few views for that I have enabled #login_required.
I wanted to follow the flask dance testing docs but I could not get it to work. Because I am only using unittest and not pytest. I also use github and not google as in the docs. So is sess['github_oauth_token'] correct? A prototype sample test could look like the following:
def test_sample(self):
with self.client as client:
with client.session_transaction() as sess:
sess['github_oauth_token'] = {
'access_token': 'fake access token',
'id_token': 'fake id token',
'token_type': 'Bearer',
'expires_in': '3600',
'expires_at': self.time + 3600
}
response = client.post(url_for('core.get_sample'), data=self.fake_sample)
self.assertRedirects(response, url_for('core.get_sample'))
The assertRedirect fails because I am redirected to the login page http://localhost/login/github?next=%2Fsample%2F and not url_for('core.get_sample').
Then tried to simply disable it, by following the official flask login docs.
It can be convenient to globally turn off authentication when unit
testing. To enable this, if the application configuration variable
LOGIN_DISABLED is set to True, this decorator will be ignored.
But this does not work as well, the test still fail because login_required is somehow executed.
So my questions are:
Because I am using github and not google as in the docs is github_oauth_token the correct key for the session?
How do I test views that have the #login_required decorator with unittest when using Flask Dance?
Edit: LOGIN_DISABLED=True works as long as I define it in my config class I use for app.config.from_object(config['testing']), what did not work was to set self.app.config['LOGIN_DISABLED'] = True in my setup method.
Even if you're using the unittest framework for testing instead of pytest, you can still use the mock storage classes documented in the Flask-Dance testing documentation. You'll just need to use some other mechanism to replace the real storage with the mock, instead of the monkeypatch fixture from Pytest. You can easily use the unittest.mock package instead, like this:
import unittest
from unittest.mock import patch
from flask_dance.consumer.storage import MemoryStorage
from my_app import create_app
class TestApp(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.client = self.app.test_client()
def test_sample(self):
github_bp = self.app.blueprints["github"]
storage = MemoryStorage({"access_token": "fake-token"})
with patch.object(github_bp, "storage", storage):
with self.client as client:
response = client.post(url_for('core.get_sample'), data=self.fake_sample)
self.assertRedirects(response, url_for('core.get_sample'))
This example uses the application factory pattern, but you could also import your app object from somewhere else and use it that way, if you want.

Access flask app endpoints in another python file?

I have a python file which defines some endpoints using flask each doing some computation and return a JSON (POST method). I want to do unit testing on this in order to do this I want to be able to access the app I created in one python file in another file so I can test my endpoints.
I see a lot of this on the internet :
from source.api import app
from unittest import TestCase
class TestIntegrations(TestCase):
def setUp(self):
self.app = app.test_client()
def test_thing(self):
response = self.app.get('/')
assert <make your assertion here>
It doesn't explain how I can define and access my app in another file. This might be a stupid question but I really don't see how.
My app is defined as follows:
from flasgger import Swagger
from flask import Flask, jsonify, request
from flask_cors import CORS
import os
def init_deserializer_restful_api():
# Initiate the Flask app
app = Flask(__name__)
Swagger(app)
CORS(app)
# Handler for deserializer
#app.route("/deserialize", methods=['POST'])
def handle_deserialization_request():
pass
I have many other end points in this fashion. Should i just do:
import my_file_name
Thanks!!
Check out this question: What does if __name__ == "__main__": do?
As long as you have that in your python program, you can both treat it as a module and you can call it directly.

Flask not registering routes

I have the following code that doesn't seem to be registering routes.
#!/usr/bin/python
import os,sys,json
from flask import Flask, url_for
class Foo:
server = Flask(__name__)
mountpoint = "/api"
def __init__(self,startup="run",config="",mountpoint="/api"):
self.mountpoint = mountpoint
self.startup = startup
self.server = server
#server.route("/test")
def test(self):
return "Hello world!"
def getEndpoints(self):
server = self.server
with server.test_request_context():
print url_for('test')
def start(self,**kwargs):
getattr(self.server,self.startup)(**kwargs)
if __name__ == "__main__":
mon = Foo()
mon.getEndpoints()
mon.start(host='0.0.0.0',debug=True)
If I don't call "getEndpoints" things seem to run just fine, but /test returns a 404. When calling "getEndpoints" it returns a build error. I'm guessing it's something simple like Why am I getting 404 error in Flask? or something scope related, but I'm still relatively new to python so I'm a little lost as to what the problem might be.
I ran your code on my machine and found that removing the self argument from your test function caused /test to work fine. In my experience, the arguments to your view functions are values passed in by the #route decorator.
See the Flask docs on routing.

Categories

Resources