Test that all endpoints have a certain header set in response

Issue

I have added a middleware to my flask rest api app to add a specific header to all responses, using the after_request() decorator. What would be a good way to ensure that all endpoints include this header? I have tests for every endpoint to test the status and data of the response. I could add an extra assert in every single test to check the header too? It is of course possible that I forget to add an assert for a certain endpoint, but I dont know of a better way to test this? Any suggestions?

Solution

I think the best way is to create a separate test, calculate registered routes and check only header and response statutes. Here is an example:

# app.py
import random
from flask import Flask, jsonify

app = Flask(__name__)


# a few routes for demo
@app.route('/user/<user_id>', methods=['GET'])
def get_user(user_id):
    return jsonify(dict(user_id=user_id))


@app.route('/user', methods=['POST'])
def create_user():
    return jsonify(dict(user_id=random.randint(0, 100000))), 201


@app.after_request
def after_request_func(response):
    # just an example - custom header
    response.headers['MY_CUSTOM_HEADER'] = 'value'
    return response

test.py:

import unittest
from parameterized import parameterized

from app import app


def get_routes_params() -> list:
    # you can move routes_map(config) to yaml and parse config before tests...
    # the test will be failed if you registered a new route(or method) and didn't set parameters
    routes_map = {
        '/user/<user_id>': {
            'GET': (dict(user_id=1), None, 200),
        },
        '/user': {
            'POST': (dict(), dict(name='Baz'), 201),
        }
    }

    params = []
    # search parameters for all registered routes from our routes_map(config)
    for rule in app.url_map.iter_rules():
        if rule.rule.startswith('/static/'):
            continue

        for method in rule.methods:
            if method in ('HEAD', 'OPTIONS'):
                continue

            route_args, json, expected_status = routes_map[rule.rule][method]
            url = rule.rule
            # replace positional route args
            for key, value in route_args.items():
                url = url.replace(f'<{key}>', str(value), 1)

            params.append([url, method.lower(), json, expected_status])
    # params for each route: [['/user/1', 'get', None, 200], ['/user', 'post', {'name': 'Baz'}, 201]]
    return params


app.config.update({'TESTING': True})
class TestMyCustomHeader(unittest.TestCase):

    @parameterized.expand(get_routes_params())
    def test_after_request_my_custom_header(self, url: str, method: str, json: dict | None, expected_status: int):
        with app.test_client() as client:
            response = getattr(client, method)(url, json=json)
            self.assertEqual(response.headers['MY_CUSTOM_HEADER'], 'value')
            self.assertEqual(response.status_code, expected_status)

So in this case you’ll have a failed tests if you add a new routes(or methods) because routes calculates dynamically. All what you need is just actualize config(routes_map). Other tests will only check specific user cases, data structures, responses(positive/negative) etc.

Answered By – Danila Ganchar

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