use mock server in testing flask app using pytest

Issue

tl;dr
I am looking for the best practice for testing a flask app which uses external APIs under its routes, and probably this is where I have to simulate the external APIs using mock servers in the testing stage.


Ok, all I have got searching pytest-mock and similar packages is that I can mock a function and have the results prepared for test assertions. But unfortunately I cannot find a best-practice explanation for my situation:

I have a flask app, under some of the routes of which I use external APIs and I want to use mock servers replacing those real external API URLs. I have come to two apparently fair solutions:

Using test URLs when testing

What I have got so far is that I can set different URLs dynamically by app.config so that in my conftest.py I can invoke using test URLs, and in reality, my app will be launched by the main URLs. Now my question here is how I can run a mock server automatically in the pytest fixture setup stage to occupy the test URLs localhost ports. I said automatically because I can naively run a manual mock app in background myself, which is silly and useless for automated testing purposes

Using a mock function instead of sending the request to the main URL

I have realized that I can setup a mocker which is a function returning my desired value, and use it alongside calling my to-be-tested app. but the point is I want to use the mock inside my app, not comparing the returning value with a mock generated value

So where am I and what should I do?!

Here is a sample:

My flask app:

app = Flask(__name__)

@app.route('/test')
def test():
    value = request.args.get('dummy_val')

    # using external APIs
    # This is where I need mock server
    output = requests.get(
         'http://external.api/some/url',
         params={'val':value}
    )
    return output*2

my test file:

def test_can_double_value(test_client):
    result = test_client('/test', query_string={'dummy_val':'foo'})
    # test if status code == 200
    # test if string equals to something
    # blah blah blah

Solution

Thanks to jonrsharpe I now am aware that this can be done using responses library. Now if we want to mock an API in the fixture, it can be done as follows:

in conftest.py

import response
from pytest import fixture

@fixture
def client():
    # add response
    responses.add(responses.GET,
        'http://mock.api/v',
        json={'key': 'value'},
        status=200
    )
    # do fixture stuff -> here it is yielding app test client
    app.config['TESTING'] = True
    client = app.test_client() 

    yield client

in test_module.py

import response

@response.activate
def test_can_connect(client):
    result = client.get('/test')
    assert result.status_code == 200

and in app.py

@app.route('/test')
def test():
    res = requests.get('http://mock.api/v')
    # blah blah

Comment in responses documentation it is suggested that we add a response in the test function. But as I wanted to add it in a fixture, this is how I did it.

Answered By – Alireza

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published