This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Integration Testing
Loading…
Integration Testing
Relevant source files
- extensions/muxio-rpc-service-caller/src/lib.rs
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-rpc-service-caller/tests/dynamic_channel_tests.rs
- extensions/muxio-tokio-rpc-client/src/rpc_client.rs
- extensions/muxio-tokio-rpc-client/tests/transport_state_tests.rs
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
This document describes integration testing patterns in Muxio that validate full client-server communication flows over real network connections. Integration tests verify the complete RPC stack from method invocation through binary framing to response decoding, ensuring all layers work correctly together.
For unit testing of individual components like RpcDispatcher and RpcStreamDecoder, see Unit Testing. For end-to-end application examples, see WebSocket RPC Application.
Purpose and Scope
Integration tests in Muxio validate:
- Complete RPC request/response cycles across real network connections
- Interoperability between different client implementations (native Tokio and WASM) with the server
- Proper handling of large payloads that require chunking and streaming
- Error propagation from server to client through all layers
- WebSocket transport behavior under realistic conditions
These tests use actual RpcServer instances and establish real WebSocket connections, providing high-fidelity validation of the entire system.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-20
Integration Test Architecture
Three-Component Test Setup
Muxio integration tests for WASM clients use a three-component architecture to simulate realistic browser-server communication within a Tokio test environment:
Component Roles:
graph TB
subgraph "Test Environment (Tokio Runtime)"
Server["RpcServer\n(muxio-tokio-rpc-server)\nListening on TCP"]
Bridge["WebSocket Bridge\ntokio-tungstenite\nConnect & Forward"]
Client["RpcWasmClient\nRuntime-agnostic\nCallback-based"]
end
Client -->|Callback: send bytes| Bridge
Bridge -->|WebSocket Binary| Server
Server -->|WebSocket Binary| Bridge
Bridge -->|dispatcher.read_bytes| Client
TestCode["Test Code"] -->|Echo::call| Client
Server -->|Register handlers| Handlers["RpcServiceEndpointInterface\nAdd, Mult, Echo"]
style Server fill:#f9f9f9
style Bridge fill:#f9f9f9
style Client fill:#f9f9f9
| Component | Type | Purpose |
|---|---|---|
RpcServer | Real server instance | Actual production server from muxio-tokio-rpc-server |
RpcWasmClient | Client under test | WASM-compatible client that sends bytes via callback |
| WebSocket Bridge | Test infrastructure | Connects client callback to server socket via real network |
This architecture ensures the WASM client is tested against the actual server implementation rather than mocks, providing realistic validation.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-19 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-78
WebSocket Bridge Pattern
Bridge Implementation
The WebSocket bridge connects the RpcWasmClient’s callback-based output to a real WebSocket connection. This pattern is essential because WASM clients are designed to be runtime-agnostic and cannot directly create network connections in a Tokio test environment.
sequenceDiagram
participant Test as "Test Code"
participant Client as "RpcWasmClient"
participant ToBridge as "to_bridge_rx\ntokio_mpsc channel"
participant WsSender as "WebSocket\nSender"
participant WsReceiver as "WebSocket\nReceiver"
participant Server as "RpcServer"
Test->>Client: Echo::call(data)
Client->>Client: encode_request()
Client->>ToBridge: Callback: send(bytes)
Note over ToBridge,WsSender: Bridge Task 1: Client → Server
ToBridge->>WsSender: recv() bytes
WsSender->>Server: WsMessage::Binary
Server->>Server: Process RPC
Server->>WsReceiver: WsMessage::Binary
Note over WsReceiver,Client: Bridge Task 2: Server → Client
WsReceiver->>Client: dispatcher.read_bytes()
Client->>Test: Return decoded result
Bridge Setup Code
The bridge consists of two async tasks and a channel:
- Client Output Channel - Created when constructing
RpcWasmClient:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-86
- Bridge Task 1: Client to Server - Forwards bytes from callback to WebSocket:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:98-108
- Bridge Task 2: Server to Client - Forwards bytes from WebSocket to dispatcher:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:110-123
Blocking Dispatcher Access
A critical detail is the use of task::spawn_blocking for dispatcher operations. The RpcDispatcher uses a blocking mutex (parking_lot::Mutex) because the core Muxio library is non-async to support WASM. In a Tokio runtime, blocking operations must be moved to dedicated threads:
This prevents freezing the async test runtime while maintaining compatibility with the non-async core design.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-123 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:116-120
Server Setup for Integration Tests
Creating the Test Server
Integration tests create a real RpcServer listening on a random port:
The server is wrapped in Arc for safe sharing between the test code and the spawned server task:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:42-48
Handler Registration
Handlers are registered using the RpcServiceEndpointInterface:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:51-69
The server is then spawned in the background:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:72-78
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:42-78
graph TB Test["Test Code"] -->|join!| Calls["Multiple RPC Calls"] Calls --> Add1["Add::call\nvec![1.0, 2.0, 3.0]"] Calls --> Add2["Add::call\nvec![8.0, 3.0, 7.0]"] Calls --> Mult1["Mult::call\nvec![8.0, 3.0, 7.0]"] Calls --> Mult2["Mult::call\nvec![1.5, 2.5, 8.5]"] Calls --> Echo1["Echo::call\nb'testing 1 2 3'"] Calls --> Echo2["Echo::call\nb'testing 4 5 6'"] Add1 --> Assert1["assert_eq! 6.0"] Add2 --> Assert2["assert_eq! 18.0"] Mult1 --> Assert3["assert_eq! 168.0"] Mult2 --> Assert4["assert_eq! 31.875"] Echo1 --> Assert5["assert_eq! bytes"] Echo2 --> Assert6["assert_eq! bytes"]
Test Scenarios
Success Path Testing
Basic integration tests validate correct request/response handling for multiple concurrent RPC calls:
The test makes six concurrent RPC calls using tokio::join!:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:126-142
This validates that:
- Multiple concurrent requests are correctly multiplexed
- Request/response correlation works via
request_idmatching - Type-safe encoding/decoding produces correct results
- The
RpcDispatcherhandles concurrent streams properly
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-142
sequenceDiagram
participant Test as "Test Code"
participant Client as "RpcWasmClient"
participant Bridge as "WebSocket Bridge"
participant Server as "RpcServer"
participant Handler as "Failing Handler"
Test->>Client: Add::call(vec![1.0, 2.0, 3.0])
Client->>Bridge: RpcRequest binary
Bridge->>Server: WebSocket message
Server->>Handler: Invoke handler
Handler->>Handler: Return Err("Addition failed")
Handler->>Server: Error response
Server->>Bridge: RpcResponse with error code
Bridge->>Client: Binary frames
Client->>Client: Decode RpcServiceError
Client->>Test: Err(RpcServiceError::Rpc)
Test->>Test: match error.code\nassert_eq! System
Error Propagation Testing
Integration tests verify that server-side errors propagate correctly through all layers to the client:
Error Handler Registration
The test registers a handler that always fails:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:155-160
Error Assertion
The test verifies the error type, code, and message:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:210-226
This validates:
RpcServiceErrorserialization and deserialization- Error code preservation (
RpcServiceErrorCode::System) - Error message transmission
- Proper error variant matching on client side
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:144-227
graph TB
Test["Test: Large Payload"] --> Create["Create payload\n200 × DEFAULT_SERVICE_MAX_CHUNK_SIZE"]
Create --> Call["Echo::call(client, large_payload)"]
Call --> Encode["RpcCallPrebuffered\nencode_request()"]
Encode --> Check{"Size >= chunk_size?"}
Check -->|Yes| Payload["rpc_prebuffered_payload_bytes"]
Check -->|No| Param["rpc_param_bytes"]
Payload --> Stream["RpcDispatcher\nStream as chunks"]
Stream --> Frames["Hundreds of binary frames"]
Frames --> Server["Server receives & reassembles"]
Server --> Echo["Echo handler returns"]
Echo --> Response["Stream response chunks"]
Response --> Client["Client reassembles"]
Client --> Assert["assert_eq! result == input"]
Large Payload Testing
Chunked Streaming Validation
A critical integration test validates that large payloads exceeding the chunk size are properly streamed as multiple frames:
Test Implementation
The test creates a payload 200 times the chunk size to force extensive streaming:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:295-311
What This Tests
| Layer | Validation |
|---|---|
RpcCallPrebuffered | Large argument handling logic (extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:58-65) |
RpcDispatcher | Request chunking and response reassembly |
RpcSession | Stream multiplexing with many frames |
| Binary Framing | Frame encoding/decoding at scale |
| WebSocket Transport | Large binary message handling |
| Server Endpoint | Payload buffering and processing |
This end-to-end test validates the complete stack can handle payloads requiring hundreds of frames without data corruption or performance issues.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:229-312 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:30-73
Connection Lifecycle Testing
Handle Connect Pattern
Integration tests must explicitly call handle_connect() to simulate the browser’s WebSocket onopen event:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs95
Without this call, the client remains in Disconnected state and RPC calls will fail. This pattern reflects the WASM client’s design where connection state is managed by the JavaScript runtime in production.
Test Timing Considerations
Tests include explicit delays to ensure server initialization:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs80
This prevents race conditions where the client attempts to connect before the server is listening.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs80 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs95
Common Integration Test Patterns
Test Structure Template
All integration tests follow a consistent structure:
Code Structure
-
Server Initialization:
- Create
TcpListenerwith port 0 for random port - Wrap
RpcServerinArcfor shared ownership - Register handlers via
endpoint.register_prebuffered() - Spawn server with
tokio::spawn
- Create
-
Client and Bridge Creation:
- Create
tokio_mpscchannel for callback output - Instantiate
RpcWasmClientwith callback closure - Connect to server using
tokio_tungstenite::connect_async - Call
client.handle_connect()to mark connection ready
- Create
-
Bridge Tasks:
- Spawn task forwarding from channel to WebSocket
- Spawn task forwarding from WebSocket to dispatcher (using
spawn_blocking)
-
Test Execution:
- Use
tokio::join!for concurrent RPC calls - Use
async moveblocks where necessary for ownership
- Use
-
Assertions:
- Validate success results with
assert_eq! - Match error variants explicitly
- Check error codes and messages
- Validate success results with
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-312
Comparison with Unit Tests
Integration tests differ fundamentally from unit tests in Muxio:
| Aspect | Unit Tests | Integration Tests |
|---|---|---|
| Components | Mock RpcServiceCallerInterface | Real RpcServer and RpcWasmClient |
| Transport | In-memory channels | Real WebSocket connections |
| Response Simulation | Manual sender injection | Server processes actual requests |
| Scope | Single component (e.g., dispatcher) | Complete RPC stack |
| Example File | extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs | extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs |
Unit Test Pattern
Unit tests use a MockRpcClient that provides controlled response injection:
extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:24-28
The mock allows direct control of responses without network communication:
extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:110-121
This approach is suitable for testing client-side logic in isolation but does not validate network behavior or server processing.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:20-93 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-20
Best Practices
Resource Management
- Use
Arcfor Server Sharing:
This prevents server from being moved into the spawn closure while still allowing endpoint access.
-
Proper Channel Cleanup: WebSocket bridge tasks naturally terminate when channels close, preventing resource leaks.
-
Use
spawn_blockingfor Synchronous Operations: Always wrap blocking dispatcher operations intask::spawn_blockingto prevent runtime stalls.
Test Reliability
-
Random Port Allocation: Use port 0 to avoid conflicts:
TcpListener::bind("127.0.0.1:0") -
Server Initialization Delay: Include brief sleep after spawning server:
tokio::time::sleep(Duration::from_millis(200)) -
Explicit Connection Lifecycle: Always call
client.handle_connect()before making RPC calls -
Concurrent Call Testing: Use
tokio::join!to validate request multiplexing and correlation
Error Testing
-
Test Multiple Error Codes: Validate
NotFound,Fail, andSystemerror codes separately -
Check Error Message Preservation: Assert exact error messages to ensure proper serialization
-
Test Both Success and Failure: Each integration test suite should include both happy path and error scenarios
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-312
Integration Test Limitations
What Integration Tests Do Not Cover
Integration tests in Muxio focus on RPC protocol correctness over real network connections. They do not address:
-
Browser JavaScript Integration:
- Tests run in Tokio, not actual browser environment
wasm-bindgenJavaScript glue code not exercised- Browser WebSocket API behavior not validated
-
Network Failure Scenarios:
- Connection drops and reconnection logic
- Network partition handling
- Timeout behavior under slow networks
-
Concurrent Client Stress:
- High connection count scenarios
- Server resource exhaustion
- Backpressure handling at scale
-
Security:
- TLS/SSL connection validation
- Authentication and authorization flows
- Message tampering detection
For end-to-end browser testing, see JavaScript/WASM Integration. For performance testing, see Performance Optimization.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-20