This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Unit Testing
Relevant source files
- extensions/muxio-rpc-service-caller/tests/prebuffered_caller_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 covers patterns and strategies for unit testing individual components within the rust-muxio system. Unit tests focus on testing isolated functionality without requiring external services, network connections, or complex integration setups. These tests validate core logic such as RpcDispatcher request correlation, service caller interfaces, and custom service method implementations.
For end-to-end testing with real clients and servers communicating over actual transports, see Integration Testing. For error handling patterns in production code, see Error Handling.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:1-213 tests/rpc_dispatcher_tests.rs:1-204
Unit Testing Architecture
The rust-muxio system is designed to enable comprehensive unit testing through several key architectural decisions:
- Runtime-agnostic core : The
muxiocore library does not depend on async runtimes, allowing synchronous unit tests - Trait-based abstractions : Interfaces like
RpcServiceCallerInterfacecan be easily mocked - Callback-based emission : The
RpcDispatcheruses callback functions for output, enabling in-memory testing - Shared service definitions : Method traits enable testing both client and server sides independently
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:20-93 tests/rpc_dispatcher_tests.rs:30-39
Testing the RPC Dispatcher
Synchronous Test Pattern
The RpcDispatcher can be tested synchronously without any async runtime because it operates on byte buffers and callbacks. This enables fast, deterministic unit tests.
Sources: tests/rpc_dispatcher_tests.rs:30-203
graph LR
subgraph "Client Side"
ClientDisp["client_dispatcher\n(RpcDispatcher)"]
OutBuf["outgoing_buf\n(Rc<RefCell<Vec<u8>>>)"]
end
subgraph "Server Side"
ServerDisp["server_dispatcher\n(RpcDispatcher)"]
IncomingBuf["incoming_buf\n(buffer chunks)"]
end
subgraph "Test Flow"
CallReq["RpcRequest"]
ReadBytes["read_bytes()"]
ProcessReq["Process request"]
Respond["RpcResponse"]
end
CallReq -->|client_dispatcher.call| ClientDisp
ClientDisp -->|emit callback| OutBuf
OutBuf -->|chunked read| IncomingBuf
IncomingBuf -->|server_dispatcher| ReadBytes
ReadBytes --> ProcessReq
ProcessReq -->|server_dispatcher.respond| Respond
Respond -->|emit to client| ClientDisp
Key Components in Dispatcher Tests
| Component | Type | Purpose |
|---|---|---|
RpcDispatcher::new() | Constructor | Creates empty dispatcher instances for client and server |
client_dispatcher.call() | Method | Initiates RPC request with callback for emitted bytes |
server_dispatcher.read_bytes() | Method | Processes incoming bytes and returns request IDs |
is_rpc_request_finalized() | Method | Checks if request is complete for prebuffered handling |
delete_rpc_request() | Method | Retrieves and removes complete request from dispatcher |
server_dispatcher.respond() | Method | Sends response back through emit callback |
Sources: tests/rpc_dispatcher_tests.rs:37-38 tests/rpc_dispatcher_tests.rs:74-123 tests/rpc_dispatcher_tests.rs:130-198
Example: Testing Request-Response Flow
The dispatcher test demonstrates a complete round-trip with multiple concurrent requests:
Sources: tests/rpc_dispatcher_tests.rs:74-123 tests/rpc_dispatcher_tests.rs:127-202
sequenceDiagram
participant Test as "Test Code"
participant ClientDisp as "client_dispatcher"
participant OutBuf as "outgoing_buf"
participant ServerDisp as "server_dispatcher"
participant Handler as "Request Handler"
Test->>ClientDisp: call(ADD_METHOD_ID)
ClientDisp->>OutBuf: emit bytes
Test->>ClientDisp: call(MULT_METHOD_ID)
ClientDisp->>OutBuf: emit bytes
Test->>OutBuf: read chunks
OutBuf->>ServerDisp: read_bytes(chunk)
ServerDisp-->>Test: rpc_request_id
Test->>ServerDisp: is_rpc_request_finalized()
ServerDisp-->>Test: true
Test->>ServerDisp: delete_rpc_request()
ServerDisp-->>Handler: RpcRequest
Handler->>Handler: decode, compute, encode
Handler->>ServerDisp: respond(RpcResponse)
ServerDisp->>ClientDisp: emit response bytes
ClientDisp->>Test: RpcStreamEvent::PayloadChunk
Code Structure for Dispatcher Tests
The test at tests/rpc_dispatcher_tests.rs:30-203 demonstrates the following pattern:
- Setup : Create client and server dispatchers with shared buffer (tests/rpc_dispatcher_tests.rs:34-38)
- Call Phase : Client dispatcher emits requests to shared buffer (tests/rpc_dispatcher_tests.rs:42-124)
- Read Phase : Server dispatcher processes chunked bytes (tests/rpc_dispatcher_tests.rs:127-132)
- Handle Phase : Extract finalized requests and process (tests/rpc_dispatcher_tests.rs:134-189)
- Respond Phase : Server dispatcher emits response back to client (tests/rpc_dispatcher_tests.rs:192-198)
Sources: tests/rpc_dispatcher_tests.rs:30-203
Testing RPC Service Callers
classDiagram
class RpcServiceCallerInterface {<<trait>>\n+get_dispatcher() Arc~TokioMutex~RpcDispatcher~~\n+get_emit_fn() Arc~Fn~\n+is_connected() bool\n+call_rpc_streaming() Result\n+set_state_change_handler()}
class MockRpcClient {
-response_sender_provider SharedResponseSender
-is_connected_atomic Arc~AtomicBool~
+get_dispatcher()
+get_emit_fn()
+is_connected()
+call_rpc_streaming()
+set_state_change_handler()
}
class TestCase {+test_buffered_call_success()\n+test_buffered_call_remote_error()\n+test_prebuffered_trait_converts_error()}
RpcServiceCallerInterface <|.. MockRpcClient
TestCase ..> MockRpcClient : uses
Mock Implementation Pattern
Testing client-side RPC logic requires mocking the RpcServiceCallerInterface. The mock implementation provides controlled behavior for test scenarios.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:24-93
Mock Components
| Component | Type | Purpose |
|---|---|---|
MockRpcClient | Struct | Test implementation of RpcServiceCallerInterface |
SharedResponseSender | Type alias | Arc<Mutex<Option<DynamicSender>>> for providing responses |
is_connected_atomic | Field | Arc<AtomicBool> for controlling connection state |
DynamicChannelType | Enum | Specifies bounded or unbounded channel for responses |
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:22-28 extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:44-85
Mock Interface Implementation
The MockRpcClient implementation at extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:30-93 shows how to mock each trait method:
get_dispatcher(): Returns a freshRpcDispatcherinstance (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:32-34)get_emit_fn(): Returns no-op closure since tests don't emit to network (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:36-38)is_connected(): Reads from sharedAtomicBoolfor state control (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:40-42)call_rpc_streaming(): Creates dynamic channel and stores sender for test control (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:44-85)
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:30-93
Testing Success Cases
Prebuffered Call Success Test
The test at extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133 validates successful RPC call flow:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133
Test Structure
- Setup mock client : Initialize with sender provider and connection state (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:99-105)
- Spawn response task : Background task simulates server sending response (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:110-121)
- Create request : Build
RpcRequestwith method ID and parameters (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:123-128) - Call RPC : Invoke
call_rpc_buffered()which awaits response (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs130) - Verify result : Assert response matches expected payload (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs132)
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133
Testing Error Cases
Remote Error Handling Test
The test at extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:135-177 validates error propagation from server to client:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:135-177
Error Types in Tests
The tests validate handling of RpcServiceError variants:
| Error Type | Code | Test Scenario |
|---|---|---|
RpcServiceError::Rpc with RpcServiceErrorCode::Fail | Business logic error | Item not found, validation failure (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:155-158) |
RpcServiceError::Rpc with RpcServiceErrorCode::System | System error | Method panic, internal error (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:197-200) |
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:155-158 extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:197-200
Testing Trait Methods
RpcMethodPrebuffered Integration
The test at extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:179-212 validates that high-level trait methods correctly propagate errors:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:179-212
Key Test Points
- Direct trait method call : Use
Echo::call()instead of lower-level APIs (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs203) - Error type preservation : Verify
RpcServiceError::Rpcvariant is maintained (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:205-210) - Error code and message : Assert both
codeandmessagefields are correct (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:207-208)
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:203-211
graph TB
subgraph "Channel Types"
Unbounded["DynamicChannelType::Unbounded\n(mpsc::unbounded)"]
Bounded["DynamicChannelType::Bounded\n(mpsc::channel with size)"]
end
subgraph "Channel Components"
DynSender["DynamicSender\n(enum wrapper)"]
DynReceiver["DynamicReceiver\n(enum wrapper)"]
end
subgraph "Test Usage"
StoreProvider["Store in\nresponse_sender_provider"]
SpawnTask["Spawned task\npolls for sender"]
SendResponse["send_and_ignore()"]
end
Unbounded --> DynSender
Bounded --> DynSender
Unbounded --> DynReceiver
Bounded --> DynReceiver
DynSender --> StoreProvider
StoreProvider --> SpawnTask
SpawnTask --> SendResponse
DynReceiver --> StoreProvider
Mock Transport Patterns
Dynamic Channel Management
Tests use dynamic channels to control response timing and simulate various scenarios:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:55-70
Channel Creation in Tests
The call_rpc_streaming() mock implementation at extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:44-85 shows the pattern:
- Match channel type : Handle both bounded and unbounded variants (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:55-70)
- Create dummy encoder : Return placeholder
RpcStreamEncoder(extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:72-80) - Store sender : Place sender in shared provider for test task access (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs82)
- Return receiver : Client code awaits responses on returned receiver (extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs84)
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:44-85
Test Data Structures
Request and Response Test Fixtures
Unit tests define custom request/response types for validation:
Sources: tests/rpc_dispatcher_tests.rs:7-28 src/rpc/rpc_request_response.rs:9-76
Encoding and Decoding in Tests
The dispatcher tests at tests/rpc_dispatcher_tests.rs:44-49 show how to use bitcode for serialization:
- Request encoding :
bitcode::encode(&AddRequestParams { numbers: vec![1.0, 2.0, 3.0] })(tests/rpc_dispatcher_tests.rs:44-46) - Response decoding :
bitcode::decode::<AddResponseParams>(&bytes)(tests/rpc_dispatcher_tests.rs:103-105) - Handler processing : Decode request, compute result, encode response (tests/rpc_dispatcher_tests.rs:151-167)
Sources: tests/rpc_dispatcher_tests.rs:44-49 tests/rpc_dispatcher_tests.rs:103-105 tests/rpc_dispatcher_tests.rs:151-167
Common Test Patterns
Pattern: Synchronous In-Memory Testing
For components that don't require async:
Sources: tests/rpc_dispatcher_tests.rs:32-203
Pattern: Async Mock with Controlled Responses
For async components requiring controlled response timing:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133
Pattern: Error Case Validation
For testing error propagation:
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:135-177
Summary
The rust-muxio unit testing approach emphasizes:
- Isolation : Core components like
RpcDispatchercan be tested without async runtimes or network connections - Mock interfaces : Trait-based design enables easy creation of test doubles for
RpcServiceCallerInterface - In-memory buffers : Shared
Rc<RefCell<Vec<u8>>>buffers simulate network transmission - Dynamic channels : Controlled response delivery via
DynamicSender/DynamicReceiver - Error validation : Comprehensive testing of both success and error paths
- Type safety : Shared service definitions ensure compile-time correctness even in tests
These patterns enable fast, reliable unit tests that validate component behavior without external dependencies.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:1-213 tests/rpc_dispatcher_tests.rs:1-204
Dismiss
Refresh this wiki
Enter email to refresh