Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/howto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,24 @@ these.
.. literalinclude :: ../tests/examples/test_howto_check.py
:language: python

To check automatically when a block finishes, use
:py:meth:`pytest_httpserver.HTTPServer.with_check`:

.. code-block:: python

httpserver.expect_ordered_request("/first").respond_with_data(status=204)
httpserver.expect_ordered_request("/second").respond_with_data(status=204)
with httpserver.with_check():
requests.get(httpserver.url_for("/first"))
requests.get(httpserver.url_for("/second"))

This checks unexpected requests even after all ordered handlers have been used.
It also propagates errors raised by response handlers. Checks run only when the
block exits normally; an exception raised in the block propagates unchanged.
The context manager does not wait for background clients, so complete their
work before leaving the block. Use ``wait()`` when you need to wait for handler
completion.

.. note::
The scope of the errors checked by the ``check()`` method may
change in the future - it is added to check all possible errors happened in
Expand Down
14 changes: 14 additions & 0 deletions pytest_httpserver/httpserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,20 @@ def check(self) -> None:
self.check_assertions()
self.check_handler_errors()

@contextmanager
def with_check(self) -> Generator[Self, None, None]:
"""
Check server-side assertions and handler errors when the block exits normally.

Calls :py:meth:`check` after the body completes. Exceptions raised in the body
propagate without running the checks, so a server-side error cannot replace them.
This context manager does not wait for requests or stop the server.

:return: the server instance
"""
yield self
self.check()

def check_assertions(self) -> None:
"""
Raise AssertionError when at least one assertion added
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/with-check-83d6f1a024ca738b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
features:
- |
Add ``HTTPServer.with_check()``, a context manager that checks server-side
assertions and handler errors when its body completes normally. It detects
unexpected requests received after the expected ordered requests without
changing ``wait()`` semantics. Exceptions raised in the body propagate
unchanged.
44 changes: 44 additions & 0 deletions tests/test_handler_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,47 @@ def handler3(_):

with pytest.raises(ValueError): # noqa: PT011
httpserver.check()


def test_with_check_success(httpserver: HTTPServer):
httpserver.expect_ordered_request("/first").respond_with_data(status=204)
httpserver.expect_ordered_request("/second").respond_with_data(status=204)

with httpserver.with_check() as server:
assert server is httpserver
assert requests.get(server.url_for("/first")).status_code == 204
assert requests.get(server.url_for("/second")).status_code == 204


def test_with_check_unexpected_request_after_expected_requests(httpserver: HTTPServer):
httpserver.expect_ordered_request("/first").respond_with_data(status=204)
httpserver.expect_ordered_request("/second").respond_with_data(status=204)

with pytest.raises(AssertionError, match="No handler found for request"), httpserver.with_check():
assert requests.get(httpserver.url_for("/first")).status_code == 204
assert requests.get(httpserver.url_for("/second")).status_code == 204
assert requests.get(httpserver.url_for("/unexpected")).status_code == 500


def test_with_check_propagates_handler_error(httpserver: HTTPServer):
handler_error = ValueError("handler failed")

def handler(_) -> werkzeug.Response:
raise handler_error

httpserver.expect_request("/error").respond_with_handler(handler)

with pytest.raises(ValueError, match="handler failed") as error, httpserver.with_check():
assert requests.get(httpserver.url_for("/error")).status_code == 500

assert error.value is handler_error


def test_with_check_preserves_body_exception(httpserver: HTTPServer):
body_error = RuntimeError("client failed")

with pytest.raises(RuntimeError, match="client failed") as error, httpserver.with_check():
assert requests.get(httpserver.url_for("/unexpected")).status_code == 500
raise body_error

assert error.value is body_error
Loading