This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Testing
Relevant source files
- 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
- src/rpc/rpc_internals/rpc_header.rs
- src/rpc/rpc_request_response.rs
- tests/rpc_dispatcher_tests.rs
Purpose and Scope
This document provides an overview of testing strategies and patterns used in the rust-muxio codebase. It covers the testing philosophy, test organization, common testing patterns, and available testing utilities. For detailed information about specific testing approaches, see Unit Testing and Integration Testing.
The rust-muxio system emphasizes compile-time correctness through shared type definitions and trait-based abstractions. This design philosophy directly influences the testing strategy: many potential bugs are prevented by the type system, allowing tests to focus on runtime behavior, protocol correctness, and cross-platform compatibility.
Testing Philosophy
The rust-muxio testing approach is built on three core principles:
Compile-Time Guarantees Reduce Runtime Test Burden : By using shared service definitions (example-muxio-rpc-service-definition), both clients and servers depend on the same RpcMethodPrebuffered trait implementations. This ensures that parameter encoding/decoding, method IDs, and data structures are consistent at compile time. Tests do not need to verify type mismatches or protocol version incompatibilities—these are caught by the compiler.
Layered Testing Mirrors Layered Architecture : The system's modular design (core multiplexing → RPC abstraction → transport implementations) enables focused testing at each layer. Unit tests verify RpcDispatcher behavior in isolation tests/rpc_dispatcher_tests.rs while integration tests validate the complete stack including network transports extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs
Cross-Platform Validation Is Essential : Because the same RPC service definitions work across Tokio-based native clients, WASM browser clients, and the server, tests must verify that all client types can communicate with the server correctly. This is achieved through parallel integration test suites that use identical test cases against different client implementations.
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-97 tests/rpc_dispatcher_tests.rs:1-30
Test Organization in the Workspace
Test Location Strategy
Tests are organized by scope and purpose:
| Test Type | Location | Purpose | Example |
|---|---|---|---|
| Core Unit Tests | tests/ in workspace root | Validate RpcDispatcher logic without async runtime | rpc_dispatcher_tests.rs |
| Integration Tests | tests/ in extension crates | Validate full client-server communication | prebuffered_integration_tests.rs |
| Test Utilities | extensions/muxio-ext-test/ | Shared test helpers and mock implementations | N/A |
| Test Service Definitions | example-muxio-rpc-service-definition/ | Shared RPC methods for testing | Add, Mult, Echo |
This organization ensures that:
- Core library tests have no async runtime dependencies
- Extension tests can use their specific runtime environments
- Test service definitions are reusable across all client types
- Integration tests exercise the complete, realistic code paths
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-18 tests/rpc_dispatcher_tests.rs:1-7
Integration Test Architecture
Integration tests create realistic client-server scenarios to validate end-to-end behavior. The following diagram illustrates the typical test setup:
Key Components
sequenceDiagram
participant Test as "Test Function\n#[tokio::test]"
participant Listener as "TcpListener\nRandom Port"
participant Server as "Arc<RpcServer>"
participant Endpoint as "RpcServiceEndpointInterface"
participant Client as "RpcClient\n(Tokio or WASM)"
participant ServiceDef as "Add/Mult/Echo\nService Definitions"
Test->>Listener: bind("127.0.0.1:0")
Test->>Server: RpcServer::new(None)
Test->>Server: server.endpoint()
Server-->>Endpoint: endpoint reference
Test->>Endpoint: register_prebuffered(Add::METHOD_ID, handler)
Test->>Endpoint: register_prebuffered(Mult::METHOD_ID, handler)
Test->>Endpoint: register_prebuffered(Echo::METHOD_ID, handler)
Test->>Test: tokio::spawn(server.serve_with_listener)
Test->>Client: RpcClient::new(host, port)
Test->>ServiceDef: Add::call(client, params)
ServiceDef->>Client: call_rpc_buffered(request)
Client->>Server: WebSocket binary frames
Server->>Endpoint: dispatch by METHOD_ID
Endpoint->>Endpoint: execute handler
Endpoint->>Client: response frames
Client->>ServiceDef: decode_response
ServiceDef-->>Test: Result<f64>
Test->>Test: assert_eq!(result, expected)
Random Port Binding : Tests bind to 127.0.0.1:0 to obtain a random available port, preventing conflicts when running multiple tests in parallel extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:21-23
Arc-Wrapped Server : The RpcServer is wrapped in Arc<RpcServer> to enable cloning into spawned tasks while maintaining shared state extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs28
Separate Endpoint Registration : Handlers are registered on the endpoint obtained via server.endpoint(), not directly on the server. This separation allows handler registration to complete before the server starts accepting connections extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:31-61
Background Server Task : The server runs in a spawned Tokio task, allowing the test to proceed with client operations on the main test task extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:64-70
Shared Service Definitions : Both client and server use the same Add, Mult, and Echo implementations from example-muxio-rpc-service-definition, ensuring type-safe, consistent encoding/decoding extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs1
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:16-97 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-20
Common Test Patterns
Success Case Testing
The most fundamental test pattern validates that RPC calls complete successfully with correct results:
This pattern uses tokio::join! to execute multiple concurrent RPC calls, verifying both concurrency and correctness extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-96
Error Propagation Testing
Tests verify that server-side errors are correctly propagated to clients with appropriate error codes:
This validates that errors are serialized, transmitted, and deserialized with correct error codes and messages extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:99-152
Large Payload Testing
Tests ensure that payloads exceeding the chunk size are correctly chunked and reassembled:
This pattern validates the streaming chunking mechanism for both requests and responses extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:154-203
Method Not Found Testing
Tests verify that calling unregistered methods returns the correct error code:
This ensures the server correctly identifies missing handlers extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:205-240
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:81-240 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:126-142
WASM Client Testing with WebSocket Bridge
Testing the WASM client requires special handling because it is runtime-agnostic and designed for browser environments. Integration tests use a WebSocket bridge to connect the WASM client to a real Tokio server:
Bridge Implementation Details
graph TB
subgraph "Test Environment"
TEST["Test Function\n#[tokio::test]"]
end
subgraph "Server Side"
SERVER["RpcServer\nTokio-based"]
LISTENER["TcpListener\n127.0.0.1:random"]
HANDLERS["Registered Handlers\nAdd, Mult, Echo"]
end
subgraph "Bridge Infrastructure"
WS_CONN["WebSocket Connection\ntokio-tungstenite"]
TO_BRIDGE["mpsc channel\nto_bridge_rx"]
FROM_BRIDGE["ws_receiver\nStreamExt"]
BRIDGE_TX["Bridge Task\nClient→Server"]
BRIDGE_RX["Bridge Task\nServer→Client"]
end
subgraph "WASM Client Side"
WASM_CLIENT["RpcWasmClient\nRuntime-agnostic"]
DISPATCHER["RpcDispatcher\nblocking_lock()"]
OUTPUT_CB["Output Callback\nsend(bytes)"]
end
TEST --> SERVER
TEST --> LISTENER
SERVER --> HANDLERS
TEST --> WASM_CLIENT
TEST --> TO_BRIDGE
WASM_CLIENT --> OUTPUT_CB
OUTPUT_CB --> TO_BRIDGE
TO_BRIDGE --> BRIDGE_TX
BRIDGE_TX --> WS_CONN
WS_CONN --> SERVER
SERVER --> WS_CONN
WS_CONN --> FROM_BRIDGE
FROM_BRIDGE --> BRIDGE_RX
BRIDGE_RX --> DISPATCHER
DISPATCHER --> WASM_CLIENT
The WebSocket bridge consists of two spawned tasks:
Client to Server Bridge : Receives bytes from the WASM client's output callback and forwards them as WebSocket binary messages extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:98-108:
Server to Client Bridge : Receives WebSocket messages and feeds them to the WASM client's dispatcher using spawn_blocking to avoid blocking the async runtime extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:110-123:
Why spawn_blocking : The RpcWasmClient uses synchronous locking (blocking_lock()) and synchronous dispatcher methods because it targets WASM environments where true async is not available. In tests, this synchronous code must run on a blocking thread pool to prevent starving the Tokio runtime.
Sources : extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-142 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:83-123
graph LR
CLIENT_DISP["Client Dispatcher\nRpcDispatcher"]
OUT_BUF["Outgoing Buffer\nRc<RefCell<Vec<u8>>>"]
IN_BUF["Incoming Buffer\nSame as outgoing"]
SERVER_DISP["Server Dispatcher\nRpcDispatcher"]
CLIENT_DISP -->|call request, write_cb| OUT_BUF
OUT_BUF -->|chunks 4| IN_BUF
IN_BUF -->|read_bytes chunk| SERVER_DISP
SERVER_DISP -->|respond response, write_cb| OUT_BUF
OUT_BUF -->|read_bytes| CLIENT_DISP
Unit Testing the RpcDispatcher
The core RpcDispatcher can be tested in isolation without async runtimes or network transports. These tests use in-memory buffers to simulate data exchange:
Test Structure
Unit tests create two RpcDispatcher instances representing client and server, connected via a shared buffer tests/rpc_dispatcher_tests.rs:30-38:
Request Flow : Client creates RpcRequest, calls dispatcher.call() with a write callback that appends to the buffer tests/rpc_dispatcher_tests.rs:42-124:
Server Processing : Server reads from the buffer in chunks, processes requests, and writes responses back tests/rpc_dispatcher_tests.rs:126-203:
This pattern validates framing, chunking, correlation, and protocol correctness without external dependencies.
Sources : tests/rpc_dispatcher_tests.rs:30-203 tests/rpc_dispatcher_tests.rs:1-29
Test Coverage Matrix
The following table summarizes test coverage across different layers and client types:
| Test Scenario | Core Unit Tests | Tokio Integration | WASM Integration | Coverage Notes |
|---|---|---|---|---|
| Basic RPC Call | ✓ | ✓ | ✓ | All layers validated |
| Concurrent Calls | ✗ | ✓ | ✓ | Requires async runtime |
| Large Payloads | ✓ | ✓ | ✓ | Chunking tested at all levels |
| Error Propagation | ✓ | ✓ | ✓ | Error serialization validated |
| Method Not Found | ✗ | ✓ | ✓ | Requires endpoint dispatch |
| Framing Protocol | ✓ | Implicit | Implicit | Core tests focus on this |
| Request Correlation | ✓ | Implicit | Implicit | Core dispatcher tests |
| WebSocket Transport | ✗ | ✓ | ✓ (bridged) | Extension-level tests |
| Connection State | ✗ | ✓ | ✓ | Transport-specific |
Coverage Rationale
- Core unit tests validate the
RpcDispatcherwithout runtime dependencies - Tokio integration tests validate native client-server communication over real WebSocket connections
- WASM integration tests validate cross-platform compatibility by testing the WASM client against the same server
- Each layer is tested at the appropriate level of abstraction
Sources : tests/rpc_dispatcher_tests.rs:1-203 extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs:1-241 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs:1-313
Shared Test Service Definitions
All integration tests use service definitions from example-muxio-rpc-service-definition/src/prebuffered.rs:
| Service Method | Input Type | Output Type | Purpose |
|---|---|---|---|
Add::METHOD_ID | Vec<f64> | f64 | Sum of numbers |
Mult::METHOD_ID | Vec<f64> | f64 | Product of numbers |
Echo::METHOD_ID | Vec<u8> | Vec<u8> | Identity function |
These methods are intentionally simple to focus tests on protocol correctness rather than business logic. The Echo method is particularly useful for testing large payloads because it returns the exact input, making assertions straightforward.
Method ID Generation : Each method has a unique METHOD_ID generated at compile time by hashing the method name extensions/muxio-rpc-service-caller/src/prebuffered/traits.rs18:
This ensures consistent method identification across all client and server implementations.
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs1 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs21
Running Tests
Tests are executed using standard Cargo commands:
Test Execution Environment : Most integration tests require a Tokio runtime even when testing the WASM client, because the test infrastructure (server, WebSocket bridge) runs on Tokio. The WASM client itself remains runtime-agnostic.
For detailed information on specific testing approaches, see:
- Unit Testing - Patterns for testing individual components
- Integration Testing - End-to-end testing with real transports
Sources : extensions/muxio-tokio-rpc-client/tests/prebuffered_integration_tests.rs18 extensions/muxio-wasm-rpc-client/tests/prebuffered_integration_tests.rs39
Dismiss
Refresh this wiki
Enter email to refresh