Good afternoon,
I am developing some unit tests for my Flask application. It is my first time trying to develop unit tests for a Flask app, but I am currently getting this error when trying to test GET requests.
def testTagCategories():
> response = client.get("/forum")
E AttributeError: 'function' object has no attribute 'get'
I have been struggling to find what the problem is, as I have followed all the steps required in the Flask documentation. Here is the code.
#pytest.fixture(scope='session')
def test_client():
flask_app = app()
testing_client = flask_app.test_client()
ctx = flask_app.app_context()
ctx.push()
yield testing_client
ctx.pop()
#pytest.fixture()
def client(app):
return app.test_client()
#pytest.fixture()
def runner(app):
return app.test_cli_runner()
Finally, this is one of the functions where I get the error. Thanks in advance.
def test_access():
response = client.get("/")
assert (response.status_code == 200)
I'm also learning to unit test a flask controller so take my answer with a pinch of salt.
But in the failing code (like below) there appears to be no client instance and thus you cannot call the get method.
def test_access():
response = client.get("/")
assert (response.status_code == 200)
I'm sure there is a nicer way of doing it by having the client created before every test fixture (that's how I would do it in another language I'm more familiar with like C# with setup / teardown before each test).
But something like this works for me:
from app import application
def test_access():
client = application.test_client()
response = client.get('/')
assert response.status_code == 200
html = response.data.decode()
assert 'xxx' in html
in my app directory there is an __init__.py with application = Flask(__name__) in it. That's what from app import application is importing.
Would love to hear a better solution from someone though.
Edit: On the function that is causing an error, trying changing the signature to the following:
def test_access(client):
Related
First of all I am aware of flask-testing library with LiveServerTestCase class but it hasn't updated since 2017 and GitHub full of issues of it not working neither on Windows or MacOs and I haven't found any other solutions.
I am trying to write some tests for flask app using selenium to validate FlaskForms inside this app.
Simple test like this:
def test_start(app):
driver.get("http://127.0.0.1:5000/endpoint")
authenticate(driver)
falls on selenium.common.exceptions.WebDriverException: Message: unknown error: net::ERR_CONNECTION_REFUSED error. (As far as I understood in my case app creates in #pytest.fixtures and immediately shuts down and I need to find a way to keep it running for the whole test duration)
My question is: Is it possible to to create some live server in each test that will remain working so I could call API endpoints via selenium?
Simple fixtures if it helps:
#pytest.fixture
def app():
app = create_app()
...
with app.context():
# creating db
...
yield app
also:
#pytest.fixture
def client(app):
"""Test client"""
return app.test_client()
Finally got it all working. My conftest.py
import multiprocessing
import pytest
from app import create_app
#pytest.fixture(scope="session")
def app():
app = create_app()
multiprocessing.set_start_method("fork")
return app
#pytest.fixture
def client(app):
return app.test_client()
Important note that using python <3.8 line multiprocessing.set_start_method("fork") is not necessary (as far as I understood in v.3.8 they refactored multiprocessing module so further upon without this line you would get pickle Error on windows and Mac).
And one simple test looks like
def test_add_endpoint_to_live_server(live_server):
#live_server.app.route('/tests-endpoint')
def test_endpoint():
return 'got it', 200
live_server.start()
res = urlopen(url_for('.te', _external=True))# ".te is a method path I am calling"
assert url_for('.te', _external=True) == "some url"
assert res.code == 200
assert b'got it' in res.read()
Also I am using url_for. The point is every time live server starts on a random port and url_for function generates url with correct port internally. So now live server is running and it is possible to implement selenium tests.
I have a application running which some where in the midst uses some rest API call. Now for stress test I want to replace this API call with some mock server. Is there any way to do it.
Let me try to put it programmatically so it gets some clarity. I've a some server running at port say 8080
# main server
from flask import Flask
from myapp import Myapp
app = Flask(__name__)
#app.route("/find_solution", methods=["GET"])
def solution() :
return app.sol.find_solution(), 200
def start():
app.sol = Myapp()
return app
Now this Myapp
#myapp
import requests
class Myapp:
def __init__():
self.session = requests.Session()
def find_solution():
myparameters = {"Some parameter that I filled"}
return self.session.request('GET', 'http://api.weatherstack.com/current', params=myparameters)
Now here I want to replace behavior of http://api.weatherstack.com/current without modifying code. i.e some way where I can replace call to http:api.weatherstack.com/current to my local system server.
Any help of lead is appreciated. I am using ubuntu 20.04
So for your scenario if you want to test your api flask comes with mock test client feature.
test_client = app.test_client()
test_client.post('/find_solution', headers={"Content-Type": "application/json"}, data=data)
So for this scenario you can create test cases and get test client instance inside your test case and perform tests at api level. This is a light weight test method rather than the one proposed by you
Refer to the following link for official flask documentation
https://flask.palletsprojects.com/en/1.1.x/testing/#keeping-the-context-around
Cheers
I'm using a application factory pattern, and when I tried to run my test, I get "Attempted to generate a URL without the application context being". I created a fixture to create the application:
#pytest.fixture
def app():
yield create_app()
but when I run my test
def test_get_activation_link(self, app):
user = User()
user.set_password(self.VALID_PASS)
generated_link = user.get_activation_link()
I get the above error (from the line of code url = url_for("auth.activate")). I'm also trying to figure out to have the app creation run for every test, without having to import it into every test, but I can't seem to find if that's possible.
This works for my app
import pytest
from xxx import create_app
#pytest.fixture
def client():
app = create_app()
app.config['TESTING'] = True
with app.app_context():
with app.test_client() as client:
yield client
def smoke_test_homepage(client):
"""basic tests to make sure test setup works"""
rv = client.get("/")
assert b"Login" in rv.data
So, you missed the application context.
At this year's Flaskcon there was an excellent talk about the Flask context - I highly recommend this video.
https://www.youtube.com/watch?v=fq8y-9UHjyk
I wanted to test redirection in my Bottle application. Unfortunately I did not find the way to test the redirection location. So far I was just able to test that the redirection was held, by testing that BottleException was raised.
def test_authorize_without_token(mocked_database_utils):
with pytest.raises(BottleException) as resp:
auth_utils.authorize()
Is there a way to obtain HTTP response status code or/and redirection location?
Thanks for help.
WebTest is a full-featured and easy way to test WSGI applications. Here's an example that checks for a redirect:
from bottle import Bottle, redirect
from webtest import TestApp
# the real webapp
app = Bottle()
#app.route('/mypage')
def mypage():
'''Redirect'''
redirect('https://some/other/url')
def test_redirect():
'''Test that GET /mypage redirects'''
# wrap the real app in a TestApp object
test_app = TestApp(app)
# simulate a call (HTTP GET)
resp = test_app.get('/mypage', status=[302])
# validate the response
assert resp.headers['Location'] == 'https://some/other/url'
# run the test
test_redirect()
I'm trying to unittest an application using pytest and an app factory, but I can't seem to get access to the client session object in my tests. I'm sure there's some context I'm not pushing somewhere. I push the app context in my 'app' fixture. Should I push the request context somewhere?
The following is an MWE.
mwe.py:
from flask import Flask, session
def create_app():
app = Flask(__name__)
app.secret_key = 'top secret'
#app.route('/set')
def session_set():
session['key'] = 'value'
return 'Set'
#app.route('/check')
def session_check():
return str('key' in session)
#app.route('/clear')
def session_clear():
session.pop('key', None)
return 'Cleared'
return app
if __name__ == "__main__":
mwe = create_app()
mwe.run()
conftest.py:
import pytest
from mwe import create_app
#pytest.fixture(scope='session')
def app(request):
app = create_app()
ctx = app.app_context()
ctx.push()
def teardown():
ctx.pop()
request.addfinalizer(teardown)
return app
#pytest.fixture(scope='function')
def client(app):
return app.test_client()
test_session.py:
import pytest
from flask import session
def test_value_set_for_client_request(client): # PASS
client.get('/set')
r = client.get('/check')
assert 'True' in r.data
def test_value_set_in_session(client): # FAIL
client.get('/set')
assert 'key' in session
def test_value_set_in_session_transaction(client): # FAIL
with client.session_transaction() as sess:
client.get('/set')
assert 'key' in sess
Note that running this directly works fine, I can jump around /set, /check, /clear and it behaves as expected. Similarly, the test that uses only the test client to GET the pages works as expected. Accessing the session directly however doesn't seem to.
The problem is with the way you use your test client.
First of all you don't have to create your client fixture. If you use pytest-flask, it offers a client fixture to use. If you still want to use your own client though (maybe because you don't want pytest-flask), your client fixture should act as a context processor to wrap your requests around.
So you need something like the following:
def test_value_set_in_session(client):
with client:
client.get('/set')
assert 'key' in session
information of the day: pytest-flask has a similar client fixture to yours. The difference is that pytest-flask uses a context manager to give you a client and that saves you 1 line per test
#pytest.yield_fixture
def client(app):
"""A Flask test client. An instance of :class:`flask.testing.TestClient`
by default.
"""
with app.test_client() as client:
yield client
your test with pytest-flask client
def test_value_set_in_session(client):
client.get('/set')
assert 'key' in session
Take a look at the following from conftest.py in cookiecutter-flask. It may give you some ideas.
#pytest.yield_fixture(scope='function')
def app():
"""An application for the tests."""
_app = create_app(TestConfig)
ctx = _app.test_request_context()
ctx.push()
yield _app
ctx.pop()
#pytest.fixture(scope='function')
def testapp(app):
"""A Webtest app."""
return TestApp(app)