Search code examples
pythonflaskswaggerpytestconnexion

How to test a Connexion/Flask app?


I'm using the Connexion framework for Flask to build a microservice. I would like to write tests for my application using py.test.

In the pytest-flask doc it says to create a fixture in conftest.py that creates the app like so:

conftest.py

import pytest

from api.main import create_app


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

In my test I'm using the client fixture like this:

test_api.py

def test_api_ping(client):
    res = client.get('/status')
    assert res.status == 200

However when I run py.test I get the following error message:

==================================== ERRORS ====================================
_______________________ ERROR at setup of test_api_ping ________________________

request = <SubRequest '_monkeypatch_response_class' for <Function 'test_api_ping'>>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch instance at 0x7f9f76b76518>

    @pytest.fixture(autouse=True)
    def _monkeypatch_response_class(request, monkeypatch):
        """Set custom response class before test suite and restore the original
        after. Custom response has `json` property to easily test JSON responses::
    
            @app.route('/ping')
            def ping():
                return jsonify(ping='pong')
    
            def test_json(client):
                res = client.get(url_for('ping'))
                assert res.json == {'ping': 'pong'}
    
        """
        if 'app' not in request.fixturenames:
            return
    
        app = request.getfuncargvalue('app')
        monkeypatch.setattr(app, 'response_class',
>                           _make_test_response_class(app.response_class))
E       AttributeError: 'App' object has no attribute 'response_class'

How can I make py.test work? Here is my create_app function:

main.py

import connexion


def create_app():
    app = connexion.App(__name__, port=8002,)
    app.add_api('swagger.yaml')
    return app


if __name__ == "__main__":
    create_app().run()

Solution

  • Using fixtures

    test_api.py

    import pytest
    import connexion
    
    flask_app = connexion.FlaskApp(__name__)
    flask_app.add_api('swagger.yml')
    
    
    @pytest.fixture(scope='module')
    def client():
        with flask_app.app.test_client() as c:
            yield c
    
    
    def test_health(client):
        response = client.get('/health')
        assert response.status_code == 200
    

    swagger.yml

    swagger: '2.0'
    info:
      title: My API
      version: '1.0'
    consumes:
      - application/json
    produces:
      - application/json
    schemes:
      - https
    paths:
      /health:
        get:
          tags: [Health]
          operationId: api.health
          summary: Health Check
          responses:
            '200':
              description: Status message from server describing current health
    

    api.py

    def health():
        return {'msg': 'ok'}, 200
    

    Using Swagger Tester

    Another solution using swagger-tester:

    test_api.py

    from swagger_tester import swagger_test
    
    authorize_error = {
        'get': {
            '/health': [200],
        }
    }
    
    def test_swagger():
        swagger_test('swagger.yml', authorize_error=authorize_error)
    

    Cool thing about this library is that you can use the examples provided in your spec. But I don't think it works out of the box with connexion.RestyResolver: you'll have to specify the OperationId at each endpoint.