Unit Tests for FLASK API - python

I have a basic flask API running :
u/app.route('/helloworld', methods = ['GET'])
def first_api():
hname = "hello"
lhname = "world"
print(hname+lhanme)
Now I need to add some unit tests to it, and here is my unit test file:
import json
def test_index(app, client):
res = client.get('/helloworld')
assert res.status_code == 200
assert "hello" in res.data
How can I pass value for variables hname and lhname from this unit test?
Here is my conf file for pytest:
import pytest
from app import app as flask_app
u/pytest.fixture
def app():
return flask_app
u/pytest.fixture
def client(app):
return app.test_client()

You have a little mistake in your endpoint. You want it to return the string instead of printing it. Please consider the following example:
from flask import Flask, request
flask_app = Flask(__name__)
app = flask_app
#app.route('/helloworld', methods = ['GET'])
def first_api():
hname = request.args.get("hname")
lhname = request.args.get("lname")
print(hname)
return hname + lhname
def test_index(app):
client = app.test_client()
hname = "hello"
lname = "world"
res = client.get('/helloworld?hname={}&lname={}'.format(hname, lname))
assert res.status_code == 200
assert "hello" in res.data.decode("UTF-8")
if __name__ == "__main__":
test_index(app)

Related

How to mock a function in a flask get request?

I recently wrote a webapi and i need to test a function. But im having trouble returning the mock value. Heres my hello.py:
from flask import Flask, request
import traceback
import json
app = Flask(__name__)
#app.route("/main")
def hello_world():
return "<p>Hello, World!</p>"
def determine_env(check):
if check == 'test':
return 'test'
elif check == 'hello_prod':
return 'prod'
elif check == 'hello_dev':
return 'dev'
else:
return "you must have gotten the wrong env"
#app.route("/main/rate_and_borrow_data")
def rate_and_borrow_data():
try:
print("testing request method")
print(request.args)
directory = request.args['udl']
env = determine_env(directory)
print(env)
args = request.args.getlist("args")
print(f"The arguments are: {args}")
if request.args.get("data") == 'rate':
print("the rate is in there")
return "finished"
except:
traceback.print_exc()
return json.dumps({'error': traceback.format_exc()})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=81, debug=True)
Now i want to write unittests on this api. my directory structure is as follows:
Test-flask-connectivity > app > hello.py
> tests > test_hello.py
Inside test_hello.py, i have written the following tests:
import unittest
import requests
import os
from unittest.mock import patch
from app.hello import determine_env
class TestFlaskApi(unittest.TestCase):
def setUp(self):
self.payload = {'data': 'rate', 'file_path': 'nwklgat/test', 'args': ['AAPL', '3', 'TMI'], 'url': 'http://localhost:81/main/rate_and_borrow_data',
'udl': 'test'}
#patch('app.hello.determine_env', return_value='monkeys_love_development')
def test_rate_and_borrow_data(self, m1):
url = self.payload['url']
r = requests.get(url, params=self.payload)
if __name__ == '__main__':
unittest.main()
Now, im expecting the print statement to say "monkeys love development" for the statement
env = determine_env(directory)
since i patched the above function. But its not giving me this value. Its giving me "test". May i know what i did wrong? Thanks

How to assign a value returned from flask somewhere else in python file?

I'm using flask to return a dict as following:
application = Flask(__name__)
#application.route('/calculate', methods=['POST'])
def itemadd():
thisDictionary = {}
body = request.get_json()
total = str(getAnswer("payload","6148ece5989a301f24ee9934"))
thisDictionary["value"] = total
return thisDictionary
if __name__ == '__main__':
application.run(host='0.0.0.0')
I want to use thisDictionary somewhere else in the file, for example:
newDictionary = thisDictionary
Put thisDictionary into global scope.
application = Flask(__name__)
thisDictionary = {}
#application.route('/calculate', methods=['POST'])
def itemadd():
body = request.get_json()
total = str(getAnswer("payload","6148ece5989a301f24ee9934"))
thisDictionary["value"] = total
return thisDictionary
if __name__ == '__main__':
application.run(host='0.0.0.0')

can't initialize values before starting App in Flask

I'm playing with Flask a little, for my application I would require a global storage which is updated by a Thread running in the background on the server. I found this question about global context and the answer from Johan Gov seems to work if I init the server using /create explicitly:
from flask import Flask
from flask.json import jsonify
app = Flask(__name__)
cache = {}
#app.route("/create")
def create():
cache['foo'] = 0
return jsonify(cache['foo'])
#app.route("/increment")
def increment():
cache['foo'] = cache['foo'] + 1
return jsonify(cache['foo'])
#app.route("/read")
def read():
return jsonify(cache['foo'])
if __name__ == '__main__':
app.run()
If I try to call the init automaticaly however, it fails as apparently no cache["foo"] is known.
from flask import Flask
from flask.json import jsonify
app = Flask(__name__)
cache = {}
def create(): #create does not have a route anymore
cache['foo'] = 0
#app.route("/increment")
def increment():
cache['foo'] = cache['foo'] + 1
return jsonify(cache['foo'])
#app.route("/read")
def read():
return jsonify(cache['foo'])
if __name__ == '__main__':
create() #initialize by default
app.run()
Why is this happening? How can I initialize global state before starting the Application?
You can use the Cache as your app property, i always use this when i want to avoid awkward global definitions, just define the cache like this:
# here u create a "cache" attribute for the app.
app.cache = {}
app.cache['foo'] = 0
# then after that when calling in a route:
# notice that we don't need the global keyword since we are using the app.
#app.route("/increment")
def increment():
app.cache = app.cache + 1
return jsonify(app.cache)
I even used relatively big objects like deep learning models using this method and had not problems at all.
tbh, the above code work for me without any change and I'm able to read and increment counter.
Try below code with global variable
from flask import Flask
from flask.json import jsonify
app = Flask(__name__)
cache = {}
def create(): # create does not have a route anymore
global cache
cache['foo'] = 0
#app.route("/increment")
def increment():
global cache
cache['foo'] = cache['foo'] + 1
return jsonify(cache['foo'])
#app.route("/read")
def read():
return jsonify(cache['foo'])
if __name__ == '__main__':
create() # initialize by default
app.run()

Input from another file

I've got 2 python files. This is the first one:
class Downloader():
def __init__(self):
baseURL = 'https://example.com'
def getDownloadLink(self):
#linkBase = input("Text: ")
responseBase = requests.get(linkBase).content
soupBase = BeautifulSoup(responseBase, 'lxml')
And second python file:
from flask import Flask
from flask import request
from flask import render_template
from firstFile import Downloader
app = Flask(__name__)
#app.route('/')
def my_form():
return render_template("form.html")
#app.route('/', methods=['POST'])
def my_form_post():
linkBase = request.form['text']
#processed_text = text.upper()
return Downloader().getDownloadLink()
if __name__ == '__main__':
app.run()
It gives me error:
NameError: name 'linkBase' is not defined
Is it possible to connect linkBase from first file with linkBase in second file ?
The problem here is that you're trying to access a variable that doesn't exist in the scope of your getDownloadLink function.
One solution would be to add linkBase as an argument:
def getDownloadLink(self, linkBase):
responseBase = requests.get(linkBase).content
soupBase = BeautifulSoup(responseBase, 'lxml')
And then modify your route to send the value to the function:
#app.route('/', methods=['POST'])
def my_form_post():
linkBase = request.form['text']
return Downloader().getDownloadLink(linkBase)
Modify your code to pass the value as an argument:
class Downloader():
def __init__(self):
baseURL = 'https://example.com'
def getDownloadLink(self, linkBase):
#linkBase = input("Text: ")
responseBase = requests.get(linkBase).content
soupBase = BeautifulSoup(responseBase, 'lxml')
Second file:
from flask import Flask
from flask import request
from flask import render_template
from firstFile import Downloader
app = Flask(__name__)
#app.route('/')
def my_form():
return render_template("form.html")
#app.route('/', methods=['POST'])
def my_form_post():
linkBase = request.form['text']
#processed_text = text.upper()
return Downloader().getDownloadLink(linkBase)
if __name__ == '__main__':
app.run()

Passing argument to class instantiated by framework? (Python Flask)

I have the following setting:
import sys
from flask import Flask
from flask.ext import restful
from model import Model
try:
gModel = Model(int(sys.argv[1]))
except IndexError, pExc:
gModel = Model(100)
def main():
lApp = Flask(__name__)
lApi = restful.Api(lApp)
lApi.add_resource(FetchJob, '/')
lApp.run(debug=True)
class FetchJob(restful.Resource):
def get(self):
lRange = gModel.getRange()
return lRange
if __name__ == '__main__':
main()
Is there a way to instantiate the Model-class inside the main()-function? Here, the Flask framework instantiates the FetchJob-class, so that I cannot provide it the parameters it forwards during the instantiation process.
I don't like to have global variables as this messes up the whole design ...
I think this should work, although I'm not familiar with Flask:
import functools
def main():
try:
gModel = Model(int(sys.argv[1]))
except IndexError as pExc:
gModel = Model(100)
lApp = Flask(__name__)
lApi = restful.Api(lApp)
lApi.add_resource(functools.partial(FetchJob, gModel), '/')
lApp.run(debug=True)
class FetchJob(restful.Resource):
def __init__(self, obj, *args, **kwargs):
restfult.Resource.__init__(self, *args, **kwargs)
self.obj = obj
def get(self):
lRange = self.obj.getRange()
return lRange

Categories

Resources