Reputation: 499
I have the following problem.
I'd like to run tests on the local flask server before deploying to production. I use pytest for that. My conftest.py looks like that for the moment:
import pytest
from toolbox import Toolbox
import subprocess
def pytest_addoption(parser):
"""Add option to pass --testenv=local to pytest cli command"""
parser.addoption(
"--testenv", action="store", default="exodemo", help="my option: type1 or type2"
)
@pytest.fixture(scope="module")
def testenv(request):
return request.config.getoption("--testenv")
@pytest.fixture(scope="module")
def testurl(testenv):
if testenv == 'local':
return 'http://localhost:5000/'
else:
return 'https://api.domain.com/'
This allows me to test the production api by typing the command pytest
and to test a local flask server by typing pytest --testenv=local
This code WORKS flawlessly.
My problem is that I have to manually instantiate the local flask server from the terminal each time I want to test locally like this:
source ../pypyenv/bin/activate
python ../app.py
Now I wanted to add a fixture that initiates a terminal in the background at the beginning of the tests and closes the server down after having finished testing. After a lot of research and testing, I still cannot get it to work. This is the line I added to the conftest.py:
@pytest.fixture(scope="module", autouse=True)
def spinup(testenv):
if testenv == 'local':
cmd = ['../pypyenv/bin/python', '../app.py']
p = subprocess.Popen(cmd, shell=True)
yield
p.terminate()
else:
pass
The errors I get are from the requests package that says that there is no connection/ refused.
E requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=5000): Max retries exceeded with url: /login (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused',))
/usr/lib/python3/dist-packages/requests/adapters.py:437: ConnectionError
This means for me that the flask server under app.py is not online. Any suggestions? I am open to more elegant alternatives
Upvotes: 4
Views: 7058
Reputation: 538
I am using the following for this purpose so that testing configuration is also preserved in the test server
@pytest.fixture(scope="session")
def app():
db_fd, db_path = tempfile.mkstemp()
app = create_app({
'TESTING': True,
'DATABASE': db_path
})
yield app
os.close(db_fd)
os.unlink(db_path)
from flask import request
def shutdown_server():
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
@pytest.fixture
def server(app):
@app.route('/shutdown',methods=('POST',))
def shutdown():
shutdown_server()
return 'Shutting down server ...'
import threading
t = threading.Thread(target=app.run)
yield t.start()
import requests
requests.post('http://localhost:5000/shutdown')
References
Upvotes: 2
Reputation: 499
With a bash script (thanks @ekuusela) I now finally succeeded in what I wanted.
I added a fixture that calls the bashscript spinserver.sh
in a new terminal window. This works in ubuntu, the command is different in different environments (see Execute terminal command from python in new terminal window? for other environments).
@pytest.fixture(scope="session", autouse=True)
def client(testenv):
if testenv != 'local':
pass
else:
p = subprocess.Popen(['gnome-terminal', '-x', './spinserver.sh'])
time.sleep(3)
yield
Here the very simple bashscript
#!/bin/bash
cd ..
source pypyenv/bin/activate
python app.py
yield
, but p.kill does not really
close the window. This is acceptable for me as it does not matter
if I have to manually close a terminal window & I can even see
flask debugging if necessaryUpvotes: 0
Reputation: 5322
For local testing the Flask test_client
is a more elegant solution. See the docs on Testing. You can create a fixture for the test_client
and create test requests with that:
@pytest.fixture
def app():
app = create_app()
yield app
# teardown here
@pytest.fixture
def client(app):
return app.test_client()
And use it like this:
def test_can_login(client):
response = client.post('/login', data={username='username', password='password'})
assert response.status_code == 200
If the only problem are the manual steps, maybe consider a bash script that does your manual setup for you and after that executes pytest
.
Upvotes: 4