Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

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

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:

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:

StepComponentPurpose
1TcpListener::bind("127.0.0.1:0")Bind to random available port
2Arc::new(RpcServer::new(None))Create server with Arc for ownership
3server.endpoint()Obtain endpoint for handler registration
4endpoint.register_prebuffered()Register service method handlers
5tokio::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:

  1. Receives encoded request bytes and a context
  2. Decodes the request using MethodType::decode_request()
  3. Performs the business logic
  4. Encodes the response using MethodType::encode_response()
  5. 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&lt;u8&gt;)"]

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:

  1. Wait briefly for server to start accepting connections
  2. Create RpcClient with host and port
  3. Make RPC calls using the high-level RpcCallPrebuffered trait

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:

ComponentImplementationPurpose
Send Callbacktokio_mpsc::unbounded_channel senderCaptures bytes from WASM client
Sender Tasktokio::spawn with channel receiverForwards bytes to WebSocket
Receiver Tasktokio::spawn with WebSocket receiverForwards bytes to client dispatcher

Key bridge setup steps:

  1. Create unbounded channel for outgoing bytes: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-86
  2. Create WASM client with callback that sends to channel: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:84-86
  3. Connect to server via tokio_tungstenite: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:89-92
  4. Call client.handle_connect() to simulate JavaScript onopen event: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs95
  5. Spawn sender task: extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:98-108
  6. Spawn receiver task with spawn_blocking for 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:

  1. Server registers handlers for multiple methods
  2. Client makes concurrent calls using tokio::join!()
  3. 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:

  1. Register handler that always returns Err()
  2. Make RPC call
  3. Assert result is Err
  4. 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:

  1. Create payload: vec![1u8; DEFAULT_SERVICE_MAX_CHUNK_SIZE * 200]
  2. Register echo handler that returns received bytes
  3. Call Echo::call() with large payload
  4. 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:

  1. Start server without registering any handlers
  2. Make RPC call for a method
  3. Assert error is RpcServiceError::Rpc with RpcServiceErrorCode::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:

CommandScope
cargo testAll tests in workspace
cargo test --package muxio-tokio-rpc-clientTokio client integration tests only
cargo test --package muxio-wasm-rpc-clientWASM client integration tests only
cargo test test_success_client_server_roundtripSpecific 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 FilePurposeDependencies
muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rsNative Tokio client end-to-end testsmuxio-tokio-rpc-server, example-muxio-rpc-service-definition
muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rsWASM client end-to-end tests with bridgemuxio-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