- WebSocketTestClient – Real websocket client powered by
websockets, designed for integration tests; captures original payloads for assertions, while trace representations andTraceEvent.summary()show safe metadata. - WebSocketSimulator – In-memory fake implementing the WebSocket protocol for fast unit tests.
- Live server harness –
LiveWebSocketServerserves a real Falcon ASGI app on an ephemeral localhost port. Thelive_websocket_serverpytest fixture providesstart(app)and a loop-boundrun(awaitable, timeout=...)helper. Connect withserver.connect(path)to useWebSocketTestClientover a real RFC WebSocket connection and have the fixture track the client for teardown. - Pytest fixtures – See
tests/behaviour/*.featureandfalcon_pachinko/unittestshelpers for factory utilities.
An async test can use the server directly:
import pytest
from falcon_pachinko.testing import LiveWebSocketServer
@pytest.mark.asyncio
async def test_ping_reply() -> None:
app = build_app()
async with LiveWebSocketServer(app) as server:
async with server.connect("/ws/chat") as session:
await session.send_json({"type": "ping"})
assert await session.receive_json() == {"type": "pong"}
assert server.pop_errors() == ()
Entering the server waits for ASGI lifespan startup and listener readiness.
server.connect() tracks each client, and server teardown closes any clients
the test leaves open. server.errors returns a read-only snapshot of captured
failures; server.pop_errors() returns and acknowledges them. The harness
captures exceptions handled by Falcon, exceptions escaping the ASGI callable,
and server-task failures. Unacknowledged failures are grouped in
LiveServerError when the test body completed without an exception. If the
test body is already raising, the harness adds captured failures as notes to
that exception so the original failure stays primary.
When capture_errors=True, use a fresh Falcon app for each server instance:
the capture handler remains registered on the app after server shutdown.
A startup timeout or ordinary startup exception raises
LiveServerStartupError. A shutdown deadline is recorded as
LiveServerShutdownError and reported during teardown using the same error
handling. The harness installs its Falcon Exception capture handler when the
server starts, replacing a handler registered earlier. If the application needs
a custom handler, register it after startup and have it call
server.record_error(ex); otherwise Falcon may consume the exception before
the harness can report it. For synchronous tests, use the
live_websocket_server fixture's start(app) and
run(awaitable, timeout=...) methods instead. See the
developer guide's WebSocket lifecycle guidance
for the fixture and custom-handler details.
The websocket_simulator pytest fixture (from falcon_pachinko.testing) takes
no parameters and yields a SimulatorRouterHarness: a pre-mounted router and
app wired to inject a WebSocketSimulator into each connection. Register
routes on harness.router, then dispatch a connection with
async with harness.connect(path) as connection: .... On teardown the fixture
calls harness.discard_pending_simulator(), discarding any simulator staged
for a connection that was never established, so tests do not need to assemble a
router and simulator by hand.
Recommended strategy:
- Unit test pure resource logic with
WebSocketSimulator. - Use
pytest-bddlive-server scenarios as the required acceptance path for every WebSocket lifecycle feature. These prove that real Falcon ASGI responders keep a socket open and exchange frames over a real connection. - Keep simulator tests for fast routing, hooks, DI, and connection-manager coverage; they complement the live suite and do not replace it.
- The
lifespan_workers.featureBDD scenarios verify workers running during application lifespan; the feature file is not a fixture.