Writing unit test for a flask application using unittest - python-3.x

I am new to unit testing and I am trying to write a test for the coed I wrote which is a commenting system that saves comments and some extra information to a database. here is the code:
#app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
ip_address = request.remote_addr
entry_content = request.form.get("content")
formatted_date = datetime.datetime.today().strftime("%Y-%m-%d/%H:%M")
app.db.entries.insert({"content": entry_content, "date": formatted_date, "IP": ip_address})
return "GET method called"
I want to write a test to check the post part of it but I don't know how to pass content in post method and make sure everything is ok.
Can you please help me with this?

I took a look at your file. I wonder if there is a problem with your code which is that whatever method you requested it with, it will ALWAYS return "GET method called". Perhaps you may want to change your code to something like this:
#app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
ip_address = request.remote_addr
entry_content = request.form.get("content")
formatted_date = datetime.datetime.today().strftime("%Y-%m-%d/%H:%M")
app.db.entries.insert({"content": entry_content, "date": formatted_date, "IP": ip_address})
return "POST method called"
return "GET method called"
First create a file named test_app.py and make sure there isn't an __init__.py in your directory.
test_app.py should contain codes listed below:
import unittest
from app import app
class AppTestCase(unittest.TestCase):
def setUp(self):
self.ctx = app.app_context()
self.ctx.push()
self.client = app.test_client()
def tearDown(self):
self.ctx.pop()
def test_home(self):
response = self.client.post("/", data={"content": "hello world"})
assert response.status_code == 200
assert "POST method called" == response.get_data(as_text=True)
if __name__ == "__main__":
unittest.main()
Open your terminal and cd to your directory then run python3 app.py. If you are using windows then run python app.py instead.
Hope this will help you solve your problem.

Related

Testin a flask enfpoint which renders an HTML using unittest

I have a simple code which returns render_template("home.html") of Flask in the main route. I wonder how can I test it using unittest?
#app.route("/", methods=["GET", "POST"])
def home():
return render_template("home.html")
I personally do the following
import unittest
from whereyourappisdefined import application
class TestFoo(unittest.TestCase):
# executed prior to each test
def setUp(self):
# you can change your application configuration
application.config['TESTING'] = True
# you can recover a "test cient" of your defined application
self.app = application.test_client()
# then in your test method you can use self.app.[get, post, etc.] to make the request
def test_home(self):
url_path = '/'
response = self.app.get(url_path)
self.assertEqual(response.status_code, 200)
For more information about testing Flask applications: https://flask.palletsprojects.com/en/2.0.x/testing/

Dictionary returned in Flask displays empty curly braces

I'm trying to make a flask pipeline which receives data from a python file and sends the data to react which display them.
I currently am stuck trying to receive the data in flask after sending them via post to the URL: localhost:5000/weather-data
The data is being posted with this Code:
dummy_data = {'data': str(msg.payload.decode('iso-8859-1')),
'timestamp': datetime.datetime.now().isoformat()}
response = requests.post(url, data=dummy_data)
print(response.text)
The print result is:
{"data": "{\"region\": \"Jokkmokk\", \"temp_now\": 8.91, \"weather_now\": \"bewölkt\", \"humidity\": 50, \"wind\": 24}",
"timestamp": "2021-02-24T17:23:15.347058"}
Which is all right but then i try to receive the data and return it on the flask side with this code:
from flask import Flask, request
app = Flask(__name__)
#app.route('/')
def test():
return 'HelloWorld'
#app.route('/weather-data', methods=['POST', 'GET'])
def weather_data():
try:
data = request.form.to_dict()
print(data)
return data
except Exception as e:
print(e)
if __name__ == '__main__':
app.run(host='127.0.0.1', debug=True, port=5000)
This runs normally through and my print(data) gives the exact same dictionary back but if i take a look at localhost:5000/weather-data i only see empty curly braces {}
As a Test i tried to return the data without receivng them first with this code:
#app.route('/weather-data', methods=['POST', 'GET'])
def weather_data():
return {"data": "{\"region\": \"Fishermans City\", \"temp_now\": 6.87, \"weather_now\": \"st\\u00fcrmisch\", "humidity\": 52, \"wind\": 58}",
"timestamp": "2021-02-23T18:32:49.120861"}
Like this it perfectly worked and showed the Data on the website.
Edit:
I think this is a stupid question for some of you but because i am kinda new to this i wanted to ask if it is possible that the Data is on the Page but when i reload the Page it overwrites the data with empty curly braces?
If yes is there a way that i can keep them on the Page until i make another Post with new data?
You need to use jsonify
from flask import Flask, request
from flask import jsonify
app = Flask(__name__)
data = dict()
#app.route('/')
def test():
return 'HelloWorld'
#app.route('/weather-data', methods=['POST', 'GET'])
def weather_data():
if request.method == 'POST':
global data
data = request.form.to_dict()
return jsonify(data), 200
else:
return jsonify(data), 200
if __name__ == '__main__':
app.run(host='127.0.0.1', debug=True, port=5000)
Have you tried using json?
response = requests.post(url, json=dummy_data)
and
data = request.json

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 resolve Uncaught Exception POST and tornado.access:500

It's a basic setup of a tornado web application with the intention of reading a JSON file POSTed by a client
Originally it was a Flask web but now converted to Tornado web. Tried using tornado-cors and also set_default_headers() function still shows the same errors.
class MainHandler(CorsMixin,tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def set_default_headers(self):
self.set_header("Access-Constrol-Allow-Origin", "*")
self.set_header("Access-Constrol-Allow-Headers", "Content-Type")
self.set_header("Access-Constrol-Allow-Methods", "POST")
def post(self):
try:
data = tornado.escape.json_decode(self.request.body)
return data
except(Exception) as err:
print(str(err))
CORS_ORIGIN = "*"
CORS_HEADERS = "*"
CORS_METHODS = 'POST'

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.

Resources