Skip to content

WebSocket integration tests hang due to TestClient async broadcast incompatibility #146

@frankbria

Description

@frankbria

Problem

All 30 tests in tests/ui/test_websocket_integration.py hang indefinitely when trying to test WebSocket broadcasts. The tests fail to complete even after several minutes.

Root Cause

FastAPI's TestClient creates synchronous WebSocket sessions that are incompatible with the async manager.broadcast() method used in production code.

Technical Details

  1. TestClient WebSocket behavior:

    • websocket.send_json() - client sends to server (synchronous)
    • websocket.receive_json() - client receives from server (synchronous, blocking)
    • Runs in a background thread with its own event loop
  2. Production broadcast behavior:

    • await manager.broadcast(message, project_id) - async method
    • Calls await connection.send_json(message) on server-side WebSocket objects
    • Requires async event loop to execute
  3. The conflict:

    • When tests call manager.broadcast(), it tries to send to WebSocket objects managed by TestClient
    • TestClient's synchronous WebSocket sessions don't properly handle async sends from the manager
    • Tests hang waiting for messages that never arrive

Affected Tests

All 30 tests in tests/ui/test_websocket_integration.py:

  • TestFullSubscriptionWorkflow (3 tests)
  • TestMultiClientScenario (2 tests)
  • TestSubscribeUnsubscribeFlow (3 tests)
  • TestDisconnectCleanup (2 tests)
  • TestBackwardCompatibility (2 tests)
  • TestInvalidMessages (9 tests)
  • TestEdgeCases (9 tests)

Attempted Solutions

❌ Threading with new event loop

def broadcast_sync(manager, message, project_id=None):
    def run_broadcast():
        loop = asyncio.new_event_loop()
        loop.run_until_complete(manager.broadcast(message, project_id))
    thread = threading.Thread(target=run_broadcast)
    thread.start()
    thread.join(timeout=2.0)

Result: Still hangs. TestClient's WebSocket objects don't work across threads.

❌ Direct asyncio.run()

asyncio.run(manager.broadcast(message, project_id=1))

Result: Hangs. Creates event loop conflict with TestClient's background thread.

Recommended Solutions

Option 1: Mock the broadcast method (Fastest)

@pytest.fixture
def mock_broadcast(monkeypatch):
    async def fake_broadcast(message, project_id=None):
        # Don't actually broadcast in tests
        pass
    monkeypatch.setattr('codeframe.ui.shared.manager.broadcast', fake_broadcast)

Pros: Quick fix, tests can verify subscription logic
Cons: Doesn't test actual message delivery

Option 2: Use real WebSocket client library (Most thorough)

Replace TestClient with websockets library:

import websockets

async def test_broadcast():
    async with websockets.connect('ws://localhost:8000/ws') as ws:
        await ws.send(json.dumps({"type": "subscribe", "project_id": 1}))
        # Server can now broadcast properly
        response = await ws.recv()

Pros: Tests real WebSocket behavior
Cons: Requires running server, more complex setup

Option 3: Refactor to test broadcast logic separately

  • Test subscription management (which websockets are subscribed)
  • Test message filtering logic (which subscribers get which messages)
  • Don't test actual WebSocket send/receive in unit tests

Pros: Clean separation of concerns
Cons: Doesn't test end-to-end WebSocket flow

Recommendation

Use Option 2 (real WebSocket client) for comprehensive testing, with Option 1 (mocking) as a short-term workaround to unblock CI/CD.

Related Issues

Files

  • tests/ui/test_websocket_integration.py - All 30 tests affected
  • codeframe/ui/shared.py - ConnectionManager.broadcast() method
  • codeframe/ui/routers/websocket.py - WebSocket endpoint

Environment

  • Python 3.13.3
  • FastAPI + Starlette TestClient
  • pytest 8.4.2

Acceptance Criteria

  • All 30 WebSocket integration tests pass
  • Tests complete in <30 seconds total
  • Tests properly validate WebSocket subscription and broadcast behavior
  • CI/CD pipeline runs successfully

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingtesting

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions