1

I'm trying to write a test with pytest that tests the return of a given post request. I want to isolate the flask server and the test in the same function. Here's my code:

import threading
import requests
from flask import Flask
from flask_restful import Api
from . import UserAuthentication

def test_user_authentication():
    app = Flask(__name__)
    api = Api(app)

    api.add_resource(UserAuthentication, "/<string:stage>/api/user/authentication")

    def app_thread_function():
        app.run(port=5000, host="0.0.0.0")

    app_thread = threading.Thread(target=app_thread_function)
    app_thread.start()

    data = {"username": "xxxxxxx.xxxxxxx@xxxxxxx.com.br", "password": "xxxxxxxxxxxxxx"}
    request = requests.post(url = "http://localhost:5000/dev/api/user/authentication", data = data) 
    print("request")
    print(request)

When I run pytest, I get this error:

urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=5000): Max retries exceeded with url: /dev/api/user/authentication (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f3a7eeb7280>: Failed to establish a new connection: [Errno 111] Connection refused'))

../web-python/lib/python3.8/site-packages/urllib3/util/retry.py:439: MaxRetryError

There's nothing running on the port 5000, why can't I call the server and run it at the same time?

Ericson Willians
  • 7,606
  • 11
  • 63
  • 114

1 Answers1

2

To isolate an app for every test function, you should use @pytest.fixture decorator.

First of all, you should read Testing Flask Applications from the official docs.

Start to define, a new fixture for your app in your conftest.py, with a function scope (default), other scopes.

from myapp import create_app

@pytest.fixture
def app():
    app = create_app()
    return app

Then, inside your test function, you can call your app fixture.

def test_my_function(app):
    # Here you have a fresh app object.
    pass

Here starts another part. You need a test_client to make requests. Note that if you are testing for assertions or exceptions in your application code, you must set app.testing = True in order for the exceptions to propagate to the test client.

An easier way to do that is to use the pytest-flask which is going to create you the other fixtures that you need like client, config, and others...

Example with pytest-flask:

def test_my_function(client):
    rv = client.get('/')
    assert b'No entries here so far' in rv.data

If you take a look at pytest-flask/fixture.py on github, you can see that the client fixture is just depending on an app fixture, and returns a text_client from it.

You can still try it to do it with thread, but this way is much simpler. You do not have to care about how to end the thread. You might have another problem with the debug mode that starts the main thread to control the real app.

In the end, for each test function that calls the client fixture, you will have a new fresh isolated app and this is what you wanted.

Victor
  • 602
  • 4
  • 12
  • Man, you really really helped me, I've managed to get it to work now, I was really lost with this, there's almost nothing online about it, the docs on pytest-flask are almost non-existent. Huge thanks! – Ericson Willians Aug 06 '20 at 18:52
  • 1
    Clean Architectures in Python - Leonardo Giordani is a free book that you can find online. it t introduces a good example of Flask/pytest. Mostly Part 2 Chapter 2 HTTP API and all chapter 3 are related to testing a Flask App. Previous Part are mostly about testing (pytest/mock/patch/unit tests...) – Victor Aug 06 '20 at 19:21
  • I'll certainly read it, thanks again! I've already downloaded it! – Ericson Willians Aug 06 '20 at 19:37