Metadata-Version: 2.4
Name: mock-response-delay
Version: 2026.9.7
Summary: Simulate a slow server, and the client timeout it causes, in HTTP mocks.
Author-email: Adam Dangoor <adamdangoor@gmail.com>
License-Expression: MIT
Project-URL: Documentation, https://adamtheturtle.github.io/mock-response-delay/
Project-URL: Source, https://github.com/adamtheturtle/mock-response-delay
Keywords: httpx,mock,requests,responses,respx,testing,timeout
Classifier: Development Status :: 4 - Beta
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.12
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: beartype>=0.22.9
Provides-Extra: dev
Requires-Dist: actionlint-py==1.7.12.24; extra == "dev"
Requires-Dist: check-manifest==0.51; extra == "dev"
Requires-Dist: check-wheel-contents==0.6.3; extra == "dev"
Requires-Dist: coverage==7.16.0; extra == "dev"
Requires-Dist: deptry==0.25.1; extra == "dev"
Requires-Dist: doc8==2.0.0; extra == "dev"
Requires-Dist: doccmd==2026.9.1; extra == "dev"
Requires-Dist: furo==2025.12.19; extra == "dev"
Requires-Dist: httpx==0.28.1; extra == "dev"
Requires-Dist: httpx2==2.12.0; extra == "dev"
Requires-Dist: interrogate==1.7.0; extra == "dev"
Requires-Dist: mypy[faster-cache]==2.3.1; extra == "dev"
Requires-Dist: mypy-strict-kwargs==2026.8.25.1; extra == "dev"
Requires-Dist: no-defaults==2026.9.1; extra == "dev"
Requires-Dist: prek==0.5.2; extra == "dev"
Requires-Dist: pydocstringformatter==1.0.0; extra == "dev"
Requires-Dist: pydocstyle==6.3; extra == "dev"
Requires-Dist: pylint[spelling]==4.0.8; extra == "dev"
Requires-Dist: pylint-per-file-ignores==3.2.1; extra == "dev"
Requires-Dist: pyproject-fmt==2.29.3; extra == "dev"
Requires-Dist: pyrefly==1.2.0; extra == "dev"
Requires-Dist: pyright==1.1.411; extra == "dev"
Requires-Dist: pyroma==5.0.1; extra == "dev"
Requires-Dist: pytest==9.1.1; extra == "dev"
Requires-Dist: pytest-beartype-tests==2026.8.16; extra == "dev"
Requires-Dist: requests==2.34.2; extra == "dev"
Requires-Dist: responses==0.26.3; extra == "dev"
Requires-Dist: respx==0.23.1; extra == "dev"
Requires-Dist: ruff==0.16.6; extra == "dev"
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
Requires-Dist: shfmt-py==4.1.0; extra == "dev"
Requires-Dist: sphinx==9.1.0; extra == "dev"
Requires-Dist: sphinx-copybutton==0.5.2; extra == "dev"
Requires-Dist: sphinx-lint==1.0.2; extra == "dev"
Requires-Dist: sphinx-paramlinks==0.6; extra == "dev"
Requires-Dist: sphinx-pyproject==0.3.0; extra == "dev"
Requires-Dist: sphinx-substitution-extensions==2026.8.13.1; extra == "dev"
Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
Requires-Dist: sphinxcontrib-towncrier==0.5.0a0; extra == "dev"
Requires-Dist: strict-kwargs==2026.8.28.post2; extra == "dev"
Requires-Dist: sybil==10.1.0; extra == "dev"
Requires-Dist: towncrier==26.9.0; extra == "dev"
Requires-Dist: ty==0.0.78; extra == "dev"
Requires-Dist: types-requests==2.33.0.20260712; extra == "dev"
Requires-Dist: vale==3.20.0.0; extra == "dev"
Requires-Dist: vulture==2.16; extra == "dev"
Requires-Dist: yamlfix==1.19.1; extra == "dev"
Requires-Dist: zizmor==1.30.0; extra == "dev"
Provides-Extra: httpx
Requires-Dist: httpx>=0.27.0; extra == "httpx"
Provides-Extra: httpx2
Requires-Dist: httpx2>=2.12; extra == "httpx2"
Provides-Extra: release
Requires-Dist: check-wheel-contents==0.6.3; extra == "release"
Requires-Dist: towncrier==26.9.0; extra == "release"
Provides-Extra: requests
Requires-Dist: requests>=2.32.3; extra == "requests"
Dynamic: license-file

|Build Status| |PyPI|

mock-response-delay
===================

.. contents::
   :local:

Simulate a slow server, and the client timeout it causes, in HTTP mocks.

``responses`` and ``respx`` answer requests instantly, so a test of how code handles a slow server, or a timeout, has nothing to exercise.
This package wraps a mock's callback so that it answers as a server which takes a given number of seconds would.
A request whose read timeout is shorter than the delay waits for the timeout and then raises the same exception which the client library raises against a real slow server.
Any other request gets the response after waiting for the delay.

The waiting is done by a function which you can replace, so a test can record the waits, or advance a fake clock, instead of really sleeping.

Installation
------------

Install the extra for the client library which the code under test uses:

.. code-block:: shell

    pip install 'mock-response-delay[requests]'
    pip install 'mock-response-delay[httpx]'
    pip install 'mock-response-delay[httpx2]'

This requires Python |minimum-python-version|\+.

Usage
-----

``requests`` with ``responses``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wrap a callback before giving it to ``responses``.
A request with a read timeout shorter than the delay raises ``requests.exceptions.Timeout``:

.. code-block:: python

    """Time out against a slow server."""

    import pytest
    import requests
    import responses
    from requests import PreparedRequest

    from mock_response_delay.for_requests import delayed_responses_callback


    def slow_callback(request: PreparedRequest) -> tuple[int, dict[str, str], str]:
        """Answer any request."""
        del request
        return (200, {}, "Hello")


    waits: list[float] = []

    with responses.RequestsMock() as mock:
        mock.add_callback(
            method="GET",
            url="https://example.com/",
            callback=delayed_responses_callback(
                callback=slow_callback,
                delay_seconds=5.0,
                sleep_fn=waits.append,
            ),
        )

        with pytest.raises(expected_exception=requests.exceptions.Timeout):
            requests.get(url="https://example.com/", timeout=1.0)

        response = requests.get(url="https://example.com/", timeout=10.0)

    assert response.text == "Hello"
    assert waits == [1.0, 5.0]

``requests`` accepts the timeout as one number, or as a ``(connect, read)`` tuple.
A slow server only affects the read leg, so only the read timeout is compared with the delay.

``httpx`` with ``respx`` or ``httpx.MockTransport``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wrap a handler before giving it to ``httpx.MockTransport``, or to ``respx`` as a side effect.
A request with a read timeout shorter than the delay raises ``httpx.ReadTimeout``:

.. code-block:: python

    """Time out against a slow server."""

    import httpx
    import pytest

    from mock_response_delay.for_httpx import delayed_httpx_handler


    def slow_handler(request: httpx.Request) -> httpx.Response:
        """Answer any request."""
        del request
        return httpx.Response(status_code=200, text="Hello")


    waits: list[float] = []
    transport = httpx.MockTransport(
        handler=delayed_httpx_handler(
            handler=slow_handler,
            delay_seconds=5.0,
            sleep_fn=waits.append,
        ),
    )

    with httpx.Client(transport=transport) as client:
        with pytest.raises(expected_exception=httpx.ReadTimeout):
            client.get(url="https://example.com/", timeout=1.0)

        response = client.get(url="https://example.com/", timeout=10.0)

    assert response.text == "Hello"
    assert waits == [1.0, 5.0]

``httpx2``
~~~~~~~~~~

``httpx2`` has its own request, response and exception classes, so it has its own module, which works in the same way with ``httpx2.MockTransport``:

.. code-block:: python

    """Time out against a slow server."""

    import httpx2
    import pytest

    from mock_response_delay.for_httpx2 import delayed_httpx2_handler


    def slow_handler(request: httpx2.Request) -> httpx2.Response:
        """Answer any request."""
        del request
        return httpx2.Response(status_code=200, text="Hello")


    waits: list[float] = []
    transport = httpx2.MockTransport(
        handler=delayed_httpx2_handler(
            handler=slow_handler,
            delay_seconds=5.0,
            sleep_fn=waits.append,
        ),
    )

    with httpx2.Client(transport=transport) as client:
        with pytest.raises(expected_exception=httpx2.ReadTimeout):
            client.get(url="https://example.com/", timeout=1.0)

        response = client.get(url="https://example.com/", timeout=10.0)

    assert response.text == "Hello"
    assert waits == [1.0, 5.0]

Controlling the clock
~~~~~~~~~~~~~~~~~~~~~

``sleep_fn`` defaults to ``time.sleep``, so by default the delay is real.
The examples above record the waits instead, which keeps the test instant.
Something which advances a fake clock, such as a ``freezegun`` tick, works the same way.

Full documentation
------------------

See the `full documentation <https://adamtheturtle.github.io/mock-response-delay/>`__.

.. |Build Status| image:: https://github.com/adamtheturtle/mock-response-delay/actions/workflows/test.yml/badge.svg?branch=main
   :target: https://github.com/adamtheturtle/mock-response-delay/actions
.. |PyPI| image:: https://badge.fury.io/py/mock-response-delay.svg
    :target: https://badge.fury.io/py/mock-response-delay
.. |minimum-python-version| replace:: 3.12
