This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Integration Testing
Relevant source files
- Cargo.lock
- extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs
This page describes the patterns and practices for writing integration tests in the rust-muxio system. Integration tests verify end-to-end functionality by creating real server and client instances that communicate over actual network connections.
For unit testing patterns focused on individual components, see Unit Testing. For examples of complete applications demonstrating these patterns, see WebSocket RPC Application.
Overview
Integration tests in rust-muxio follow a full-fidelity approach: they instantiate real RpcServer instances, real client instances (RpcClient or RpcWasmClient), and communicate over actual network sockets. This ensures that all layers of the system—from binary framing through RPC dispatch to service handlers—are exercised together.
The integration test suites are located in:
- extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-241 for native Tokio client tests
- extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-313 for WASM client tests
Both test suites use the same server implementation and shared service definitions from example-muxio-rpc-service-definition, demonstrating the cross-platform compatibility of the system.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-241 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-313
Test Architecture
Diagram: Integration Test Component Architecture
Integration tests create isolated environments where a real server listens on a random TCP port. Clients connect to this server using actual WebSocket connections. For WASM clients, a bridge component forwards bytes between the client's callback interface and the WebSocket connection.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:16-97 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:39-142
Server Setup Pattern
Integration tests follow a consistent pattern for server initialization:
| Step | Component | Purpose |
|---|---|---|
| 1 | TcpListener::bind("127.0.0.1:0") | Bind to random available port |
| 2 | Arc::new(RpcServer::new(None)) | Create server with Arc for ownership |
| 3 | server.endpoint() | Obtain endpoint for handler registration |
| 4 | endpoint.register_prebuffered() | Register service method handlers |
| 5 | tokio::spawn(server.serve_with_listener()) | Spawn server in background task |
Diagram: Server Setup Sequence
The Arc<RpcServer> pattern is critical because the server needs to be cloned into the spawned task while handlers are being registered. The endpoint() method returns a reference that can register handlers before the server starts serving.
Example server setup from Tokio integration tests:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:20-71
Example server setup from WASM integration tests:
extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:42-78
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:20-71 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:42-78
Handler Registration
Service method handlers are registered using the register_prebuffered() method on the RpcServiceEndpointInterface. Each handler is an async closure that:
- Receives encoded request bytes and a context
- Decodes the request using
MethodType::decode_request() - Performs the business logic
- Encodes the response using
MethodType::encode_response() - Returns
Result<Vec<u8>, RpcServiceError>
Diagram: Handler Registration Flow
graph LR
REG["endpoint.register_prebuffered()"]
METHOD_ID["METHOD_ID\nCompile-time constant"]
HANDLER["Async Closure\n/bytes, ctx/ async move"]
DECODE["decode_request(&bytes)"]
LOGIC["Business Logic\nsum(), product(), echo()"]
ENCODE["encode_response(result)"]
REG --> METHOD_ID
REG --> HANDLER
HANDLER --> DECODE
DECODE --> LOGIC
LOGIC --> ENCODE
ENCODE --> RETURN["Ok(Vec<u8>)"]
Example Add handler registration:
This pattern is consistent across all methods (Add, Mult, Echo) and appears identically in both Tokio and WASM integration tests.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:34-61 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:51-69
Tokio Client Setup
Native Tokio clients connect directly to the server using WebSocket:
Diagram: Tokio Client Initialization
The client setup is straightforward:
- Wait briefly for server to start accepting connections
- Create
RpcClientwith host and port - Make RPC calls using the high-level
RpcCallPrebufferedtrait
Example from integration tests:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:74-96
The RpcCallPrebuffered trait provides the call() method that handles encoding, transport, and decoding automatically. See extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-98 for trait implementation.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:74-96 extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs:10-98
WASM Client Setup with Bridge
WASM client integration tests require additional infrastructure because the RpcWasmClient uses a callback-based interface rather than direct WebSocket access. The tests create a "bridge" that simulates the JavaScript glue code:
Diagram: WASM Client Bridge Architecture
graph TB
subgraph "Test Components"
TEST["Test Function"]
WASM_CLIENT["RpcWasmClient::new(callback)"]
end
subgraph "Bridge Infrastructure"
TX_CHANNEL["tokio_mpsc::unbounded_channel\nto_bridge_tx/rx"]
WS_CONNECTION["tokio_tungstenite::connect_async\nWebSocket"]
SENDER_TASK["Sender Task\nChannel → WebSocket"]
RECEIVER_TASK["Receiver Task\nWebSocket → Dispatcher"]
end
subgraph "Server"
RPC_SERVER["RpcServer\nWebSocket endpoint"]
end
TEST --> WASM_CLIENT
WASM_CLIENT -->|send_callback bytes| TX_CHANNEL
TX_CHANNEL --> SENDER_TASK
SENDER_TASK -->|Binary frames| WS_CONNECTION
WS_CONNECTION --> RPC_SERVER
RPC_SERVER -->|Binary frames| WS_CONNECTION
WS_CONNECTION --> RECEIVER_TASK
RECEIVER_TASK -->|dispatcher.read_bytes| WASM_CLIENT
The bridge consists of three components:
| Component | Implementation | Purpose |
|---|---|---|
| Send Callback | tokio_mpsc::unbounded_channel sender | Captures bytes from WASM client |
| Sender Task | tokio::spawn with channel receiver | Forwards bytes to WebSocket |
| Receiver Task | tokio::spawn with WebSocket receiver | Forwards bytes to client dispatcher |
Key bridge setup steps:
- Create unbounded channel for outgoing bytes: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-86
- Create WASM client with callback that sends to channel: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:84-86
- Connect to server via
tokio_tungstenite: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:89-92 - Call
client.handle_connect()to simulate JavaScriptonopenevent: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs95 - Spawn sender task: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:98-108
- Spawn receiver task with
spawn_blockingfor synchronous dispatcher calls: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:110-123
Important: The receiver task uses task::spawn_blocking() because the dispatcher's blocking_lock() and read_bytes() methods are synchronous. Running these on the async runtime would block the executor, so they are moved to a dedicated blocking thread.
Sources: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-123
Common Test Patterns
Success Roundtrip Test
The most basic integration test verifies successful request-response cycles:
Diagram: Success Roundtrip Test Flow
Test structure:
- Server registers handlers for multiple methods
- Client makes concurrent calls using
tokio::join!() - Test asserts each result matches expected value
Example usingtokio::join!() for concurrent calls:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-96
This pattern tests multiple methods in parallel, verifying that the multiplexing layer correctly correlates responses to requests.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:19-97 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:40-142
graph LR
HANDLER["Handler returns\nErr(String)"]
ENCODE["Error encoded as\nRpcServiceError"]
TRANSPORT["Sent via\nWebSocket"]
DECODE["Client decodes\nerror"]
MATCH["Test matches\nerror variant"]
HANDLER --> ENCODE
ENCODE --> TRANSPORT
TRANSPORT --> DECODE
DECODE --> MATCH
VARIANT["RpcServiceError::Rpc\ncode: System\nmessage: 'Addition failed'"]
MATCH --> VARIANT
Error Propagation Test
Error handling tests verify that server-side errors are correctly propagated to clients:
Diagram: Error Propagation Flow
Test pattern:
- Register handler that always returns
Err() - Make RPC call
- Assert result is
Err - Match on specific error variant and check error code and message
Example error handler registration:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:114-117
Example error assertion:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:136-151
The test explicitly matches on RpcServiceError::Rpc variant and verifies both the error code (RpcServiceErrorCode::System) and message.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:100-152 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:145-227
graph TB
CREATE["Create payload\n200 × DEFAULT_SERVICE_MAX_CHUNK_SIZE\n≈ 12.8 MB"]
CALL["Echo::call(client, large_payload)"]
subgraph "Client Side Chunking"
CLIENT_CHUNK["RpcDispatcher chunks\ninto ~200 frames"]
CLIENT_SEND["Send frames sequentially"]
end
subgraph "Server Side Processing"
SERVER_RECV["Receive and reassemble\n~200 frames"]
SERVER_ECHO["Echo handler\nreturns same bytes"]
SERVER_CHUNK["Chunk response\ninto ~200 frames"]
SERVER_SEND["Send response frames"]
end
subgraph "Client Side Reassembly"
CLIENT_RECV["Receive and reassemble\n~200 frames"]
CLIENT_RETURN["Return decoded response"]
end
CREATE --> CALL
CALL --> CLIENT_CHUNK
CLIENT_CHUNK --> CLIENT_SEND
CLIENT_SEND --> SERVER_RECV
SERVER_RECV --> SERVER_ECHO
SERVER_ECHO --> SERVER_CHUNK
SERVER_CHUNK --> SERVER_SEND
SERVER_SEND --> CLIENT_RECV
CLIENT_RECV --> CLIENT_RETURN
CLIENT_RETURN --> ASSERT["assert_eq!(result, large_payload)"]
Large Payload Test
Large payload tests verify that the system correctly chunks and reassembles payloads that exceed the maximum frame size:
Diagram: Large Payload Chunking Flow
Test implementation:
- Create payload:
vec![1u8; DEFAULT_SERVICE_MAX_CHUNK_SIZE * 200] - Register echo handler that returns received bytes
- Call
Echo::call()with large payload - Assert response equals request
Example from Tokio integration tests:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:155-203
This test exercises the framing protocol's ability to handle payloads hundreds of times larger than the maximum frame size, verifying that chunking and reassembly work correctly in both directions.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:155-203 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:230-312
graph LR
START["Start server\nNo handlers registered"]
CONNECT["Client connects"]
CALL["Add::call(client, params)"]
DISPATCH["Server dispatcher receives\nMETHOD_ID"]
LOOKUP["Lookup handler\nNot found!"]
ERROR["Return RpcServiceError::Rpc\ncode: NotFound"]
CLIENT_ERR["Client receives error"]
ASSERT["assert error code\n== NotFound"]
START --> CONNECT
CONNECT --> CALL
CALL --> DISPATCH
DISPATCH --> LOOKUP
LOOKUP --> ERROR
ERROR --> CLIENT_ERR
CLIENT_ERR --> ASSERT
Method Not Found Test
This test verifies error handling when a client calls a method that has no registered handler:
Diagram: Method Not Found Error Flow
Test pattern:
- Start server without registering any handlers
- Make RPC call for a method
- Assert error is
RpcServiceError::RpcwithRpcServiceErrorCode::NotFound
Example:
extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:206-240
This test validates that the RpcDispatcher correctly handles missing method IDs and returns appropriate error codes rather than panicking or hanging.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:206-240
Test Execution
Integration tests use the #[tokio::test] macro to run in an async context:
Running integration tests:
| Command | Scope |
|---|---|
cargo test | All tests in workspace |
cargo test --package muxio-tokio-rpc-client | Tokio client integration tests only |
cargo test --package muxio-wasm-rpc-client | WASM client integration tests only |
cargo test test_success_client_server_roundtrip | Specific test across all packages |
Note: WASM client integration tests run using native Tokio runtime with a bridge, not in actual WebAssembly. They validate the client's core logic but not WASM-specific browser APIs. For browser testing, see JavaScript/WASM Integration.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs18 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs39
Test Organization
Integration tests are organized by client type in separate test files:
Diagram: Integration Test File Organization
| Test File | Purpose | Dependencies |
|---|---|---|
muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs | Native Tokio client end-to-end tests | muxio-tokio-rpc-server, example-muxio-rpc-service-definition |
muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs | WASM client end-to-end tests with bridge | muxio-tokio-rpc-server, tokio-tungstenite, example-muxio-rpc-service-definition |
Both test files use the same service definitions (Add, Mult, Echo) from example-muxio-rpc-service-definition, demonstrating that the same RPC interface works across platforms.
The muxio-ext-test crate provides shared testing utilities (see Cargo.lock:842-855 for dependencies), though the integration tests shown here use direct dependencies on the server and service definition crates.
Sources: extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-11 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-37 Cargo.lock:842-855
Dismiss
Refresh this wiki
Enter email to refresh