Currently I'm working on a Flask project and need to make some tests.
The test I'm struggling is about Flask Sessions.
I have this view:
#blue_blueprint.route('/dashboard')
"""Invoke dashboard view."""
if 'expires' in session:
if session['expires'] > time.time():
pass
else:
refresh_token()
pass
total_day = revenues_day()
total_month = revenues_month()
total_year = revenues_year()
total_stock_size = stock_size()
total_stock_value = stock_value()
mean_cost = total_stock_value/total_stock_size
return render_template('dashboard.html.j2', total_day=total_day, <br> total_month=total_month, total_year=total_year, total_stock_size=total_stock_size, total_stock_value=total_stock_value, mean_cost=mean_cost)
else:
return redirect(url_for('blue._authorization'))
And have this test:
def test_dashboard(client):
with client.session_transaction(subdomain='blue') as session:
session['expires'] = time.time() + 10000
response = client.get('/dashboard', subdomain='blue')
assert response.status_code == 200
My currently conftest.py is:
#pytest.fixture
def app():
app = create_app('config_testing.py')
yield app
#pytest.fixture
def client(app):
return app.test_client(allow_subdomain_redirects=True)
#pytest.fixture
def runner(app):
return app.test_cli_runner(allow_subdomain_redirects=True)
However, when I execute the test, I'm getting a 302 status code instead of the expected 200 status code.
So my question is how I can pass properly the session value?
OBS: Running normally the application the if statement for session is working properly.
I find the solution and I want to share with you the answer.
In the API documentation Test Client says:
When used in combination with a with statement this opens a session transaction. This can be used to modify the session that the test client uses. Once the with block is left the session is stored back.
We should put the assert after with statement not in, for this work, so the code should be:
def test_dashboard(client):
with client.session_transaction(subdomain='blue') as session:
session['expires'] = time.time() + 10000
response = client.get('/dashboard', subdomain='blue')
assert response.status_code == 200
This simple indent solves my problem.
Related
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)
I want to know how am I supposed to test my code and see whether it works properly. I want to make sure that it stores the received data to the database. Can you please tell me how am I supposed to do that? While I was searching the forum I found this post but I did not really understand what is going on. here is the code I want to test.
client = MongoClient(os.environ.get("MONGODB_URI"))
app.db = client.securify
app.secret_key = str(os.environ.get("APP_SECRET"))
#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 render_template("home.html")
and here is the mock test I wrote:
import os
from unittest import TestCase
from app import app
class AppTest(TestCase):
# executed prior to each test
def setUp(self):
# you can change your application configuration
app.config['TESTING'] = True
# you can recover a "test cient" of your defined application
self.app = app.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)
def test_post(self):
url_path = '/'
response = self.app.post(url_path,data={"content": "this is a test"})
self.assertEqual(response.status_code, 200)
The test_post gets stuck and after some seconds gives an error when reaches app.db.entries.insert({"content": entry_content, "date": formatted_date, "IP": ip_address}) part. Please tell me also how can I retrieve the saved data in order to make sure it is saved in the expected way
This is what I do using NodeJS, not tested at all in python but the idea is the same.
First of all, find a in-memory DB, there are options like pymongo-inmemory or mongomock
Then in your code you have to do the connection according to you environment (production/development/whatever)
Something like this:
env = os.environ.get("ENV")
if env == "TESTING":
# connect to mock db
elif env == "DEVELOMPENT":
# for example if you want to test against a real DB but not the production one
# then do the connection here
else:
# connect to production DB
I don't know if it is the proper way to do it but I found a solution. After creating a test client self.app = app.test_client() the db gets set to localhost:27017 so I changed it manually as follows and it worked:
self.app = app.test_client()
client = MongoClient(os.environ.get("MONGODB_URI"))
Hi I am new to unit testing and I am digging into mocks and pytest.I am trying to unit test two rest api requests where the GET request checks if an item doesn't exist in the API and if it does not create it with POST request and to create a folder, otherwise if it exists just to create a folder.
I tried to use Mocker() and I am stuck on AttributeError: Mocker when I try to mock the GET request.
This is the code I am trying to test:
client = requests.session()
# get item by name
response = client.get(
f"https://url.com/rest/item/info?name=test_item",
auth=username, password),
)
if (
response.status_code == 200
and response.json()["status"]
== "Item doesn't exist!"
):
logging.info(f"Creating item")
client.post(
"https://url.com/rest/item/create?name=test_item",
auth=username, password),
)
# directory creation
dir_make = "mkdir -p test_item/temperature"
exec_cmd(dir_make)
elif response.status_code == 200 and response.json()["status"]=="OK":
# directory creation
dir_make = "mkdir -p test_item/temperature"
exec_cmd(dir_make)
And this is the unit test that fails with AttributeError:
def test_existing_item(requests_mock, monkeypatch):
with requests_mock.Mocker() as mock:
mock.get("https://url.com/rest/item/info?name=test_item", text="OK")
resp = requests.get("https://url.com/rest/item/info?name=test_item")
assert resp.text == "OK"
EDIT: Test for item not found and POST mock. It seems like it doesn't add coverage to the else statement. How can be tested if the item exists and only the folder needs to be added in that case?
EDIT 2: Added elif statement instead of else and 2 separate tests, still the one test_existing_items() doesn't cover the elif statement...What am I doing wrong in that case?
def test_existing_item(monkeypatch):
with requests_mock.Mocker() as mock_request:
mock_request.get(requests_mock.ANY, text="success!")
resp = requests.get(
"https://url.com/rest/item/info?name=test_item",
auth=("mock_username", "mock_password"),
)
if resp.status_code == 200 and resp.json()["status"] == "OK":
dir_make = "mkdir -p test_item/temperature"
exec_cmd(dir_make)
encoded_auth = b64encode(b"mock_username:mock_password").decode("ascii")
assert mock_request.last_request.headers["Authorization"] == f"Basic {encoded_auth}"
def test_post_item(monkeypatch):
with requests_mock.Mocker() as mock_request:
mock_request.get(requests_mock.ANY, text="success!")
resp = requests.get(
"https://url.com/rest/item/info?name=test_item",
auth=("mock_username", "mock_password"),
)
if resp.status_code == 200 and resp.json()["status"] == "ERROR":
mock_request.get(requests_mock.ANY, text="success!")
requests.post(
"https://url.com/rest/item/create?name=test_item",
auth=("mock_username", "mock_password"),
)
dir_make = "mkdir -p test_item/temperature"
exec_cmd(dir_make)
encoded_auth = b64encode(b"mock_username:mock_password").decode("ascii")
assert mock_request.last_request.headers["Authorization"] == f"Basic {encoded_auth}"
I am not familiar with unit testing so any help would be appreciated to unit test this code.
import requests
import requests_mock
def test_existing_item(monkeypatch):
with requests_mock.Mocker() as mock:
mock.get("https://url.com/rest/item/info?name=test_item", text="OK")
resp = requests.get("https://url.com/rest/item/info?name=test_item")
assert resp.text == "OK"
Don't pass requests_mock as parameter and pytest should work fine.
EDIT:
As for your edit:
It seems like it doesn't add coverage to the else statement. How can be tested if the item exists and only the folder needs to be added in that case?
That would be because your if condition is always true, so it never accesses the code below the else statement. Your second question is rather unclear to me, but I believe you want to write several tests, one for your if statement and one for your else statement. As a rule of thumb, if you need conditional logic in your test, you have a problem: either everything should go as you want it to every time you run your tests, either you should abort and have the test fail, as you want your code to have the exact same behavior every time you run it - and your tests as well, by extension.
I lately started using Flask in one of my projects to provide data via a simple route. So far I return a json file containing the data and some other information. When running my Flask app I see the status code of this request in terminal. I would like to return the status code as a part of my final json file. Is it possible to catch the same code I see in terminal?
Some simple might look like this
from flask import Flask
from flask import jsonify
app = Flask(__name__)
#app.route('/test/<int1>/<int2>/')
def test(int1,int2):
int_sum = int1 + int2
return jsonify({"result":int_sum})
if __name__ == '__main__':
app.run(port=8082)
And in terminal I get:
You are who set the response code (by default 200 on success response), you can't catch this value before the response is emited. But if you know the result of your operation you can put it on the final json.
#app.route('/test/<int1>/<int2>/')
def test(int1, int2):
int_sum = int1 + int2
response_data = {
"result": int_sum,
"sucess": True,
"status_code": 200
}
# make sure the status_code on your json and on the return match.
return jsonify(response_data), 200 # <- the status_code displayed code on console
By the way if you access this endpoint from a request library, on the response object you can find the status_code and all the http refered data plus the json you need.
Python requests library example
import requests
req = requests.get('your.domain/test/3/3')
print req.url # your.domain/test/3/3
print req.status_code # 200
print req.json() # {u'result': 6, u'status_code: 200, u'success': True}
You can send HTTP status code as follow:
#app.route('/test')
def test():
status_code = 200
return jsonify({'name': 'Nabin Khadka'}, status_code) # Notice second element of the return tuple(return)
This way you can control what status code to return to the client (typically to web browser.)
I am learning how to test functions in Python using Mock. I am trying to write a test for a simple function,
#social.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return redirect(url_for('social.index'))
else:
return render_template("/home.html")
However, I am stuck at how to try and mock session.get('uid') value. So far, this has been my attempt,
#patch('session', return_value='')
#patch('flask.templating._render', return_value='')
def test_mocked_render(self, mocked, mock_session):
print "mocked", repr(self.app.get('/social/friends').data)
print "was _render called?", mocked.called
This attempt may be completely wrong and this is definitely the wrong way as I am still not able to mock session. However, can someone please guide me in the right way through this? Thanks.
Starting with Flask 0.8 we provide a so called “session transaction” which simulates the appropriate calls to open a session in the context of the test client and to modify it.
Let's give a simple example: app.py
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'very secret'
#app.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return 'uid not in session'
else:
return 'uid in session'
if __name__ == '__main__':
app.run(debug=True)
The test file: test_app.py
import unittest
from app import app
class TestSession(unittest.TestCase):
def test_mocked_session(self):
with app.test_client() as c:
with c.session_transaction() as sess:
sess['uid'] = 'Bar'
# once this is reached the session was stored
rv = c.get('/friends')
assert rv.data == 'uid in session'
if __name__ == '__main__':
unittest.main()
run the tests via python test_app.py.
Documentation: Accessing and Modifying Sessions