Si Mon
Si Mon

Reputation: 499

Spin up a local flask server for testing with pytest

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

Answers (3)

S.aad
S.aad

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

Si Mon
Si Mon

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
  • The sleep command is necessary because the server takes some time to initialize.
  • Don't forget to make your bash script executable (chmod u+x spinserver.sh)
  • I tried to do a teardown after 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 necessary

Upvotes: 0

ekuusela
ekuusela

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

Related Questions