Skip to main content

        Async systems return request IDs, not data. Here's how Flask plus pytest gives you a clean, controllable test harness for verifying eventual responses.

Streamlining Asynchronous System Testing With Flask and pytest

Async systems return request IDs, not data. Here's how Flask plus pytest gives you a clean, controllable test harness for verifying eventual responses.

For developers and QE engineers dealing with asynchronous systems, testing can be a significant challenge. These systems often respond with only a request ID, making it difficult to verify the actual response data. However, by leveraging the power of Flask and pytest, we can create a robust testing framework that simplifies the process.

The asynchronous system challenge

In an asynchronous system, when a request is sent, the system typically responds with a request ID rather than the actual data. This ID is then used to query the system for the result at a later time. This behavior can make testing difficult, as the traditional request-response cycle is disrupted.

Introducing Flask as a mock system

Flask is a lightweight Python web framework that can be used as a mock system to simulate the asynchronous system’s behavior. In our testing setup, the system under test will send its responses to Flask instead of the actual system, or a callback route will be notified. Flask will act as a piggyback instance, storing the responses for later verification.

Flask and pytest integration

It’s no secret that I find pytest to be a powerful and flexible testing framework written in Python, making it an ideal choice for our testing needs. By integrating Flask with pytest, we can create a streamlined testing process that leverages the strengths of both tools.

Setting up the Flask mock system

Let’s start by creating a Flask application that will act as our mock system. This application will have endpoints to receive responses from the system under test and retrieve them later using the request ID.

# mock_system.py
from flask import Flask, request, jsonify

app = Flask(__name__)
responses = {}

@app.route('/receive', methods=['POST'])
def receive_response():
    data = request.get_json()
    request_id = data['request_id']
    response = data['response']
    responses[request_id] = response
    return jsonify({'status': 'success'})

@app.route('/retrieve/<request_id>', methods=['GET'])
def retrieve_response(request_id):
    if request_id in responses:
        return jsonify(responses[request_id])
    return jsonify({'error': 'Request ID not found'}), 404

In this example, we create a Flask application with two endpoints:

  • /receive - accepts POST requests containing a request ID and a response. It stores the response in a dictionary, using the request ID as the key.
  • /retrieve/<request_id> - accepts GET requests with a request ID as a parameter. It retrieves the corresponding response from the dictionary and returns it as JSON. If the request ID is not found, it returns a 404 error.

Writing tests with pytest and integration with the Flask mock

Next, we use pytest to write our tests. Within each test, we send requests to the system under test and capture the request IDs. Using these IDs, we then query the Flask mock system to retrieve the responses and assert their correctness.

# tests/test_async_system.py
import pytest
import requests

BASE_URL = 'http://localhost:5000'

@pytest.fixture(scope='module')
def mock_system():
    from mock_system import app
    app.testing = True
    with app.test_client() as client:
        yield client

def test_async_operation(mock_system):
    # Send request to the system under test
    request_id = send_request_to_system()

    # Wait for the response to be available
    # ... some logic here

    # Retrieve the response from the mock system
    response = mock_system.get(f'{BASE_URL}/retrieve/{request_id}')
    data = response.get_json()

    # Assert the response data
    assert data['status'] == 'success'
    assert data['result'] == 'expected_result'

In this test:

  1. The mock_system fixture sets up the Flask application in testing mode and yields a test client.
  2. We send a request to the system under test and capture the request ID.
  3. We wait for the response to be available (delay, polling, or callback).
  4. We use the mock_system fixture to GET /retrieve/<request_id>.
  5. We assert that the response data meets our expectations.

Benefits of this approach

Simplified testing. The Flask mock system abstracts away the complexities of the actual asynchronous system, letting us focus on testing the core functionality of our code.

Improved reliability. With Flask acting as a controlled environment, we ensure consistent test results and eliminate issues caused by the real system’s availability or state.

Rapid feedback. pytest’s fast execution and comprehensive reporting provide quick feedback, enabling us to identify and fix issues efficiently.

Scalability. As our system grows, the testing framework scales by adding more test cases and taking advantage of pytest’s parallelization and distributed testing, using more fixtures of the same mocked service.

Conclusion

Testing asynchronous systems is challenging, but Flask plus pytest gives you a robust testing framework that streamlines the process. With Flask acting as a mock and pytest handling the testing logic, you ensure reliability and correctness of your code while benefiting from rapid feedback and scalability.