This documentation is part of the "Projects with Books" initiative at zenOSmosis.
The source code for this project is available on GitHub.
Unit Testing
Loading…
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 unit testing practices and patterns within the muxio codebase. Unit tests verify individual components in isolation using mock implementations and controlled test scenarios. These tests focus on validating the behavior of core RPC components including RpcDispatcher, RpcServiceCallerInterface, and protocol-level request/response handling.
For information about end-to-end testing with real server instances and WebSocket connections, see Integration Testing. For general testing infrastructure overview, see Testing.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:1-213 tests/rpc_dispatcher_tests.rs:1-204
Unit Testing Architecture
The unit testing strategy in muxio uses isolated component testing with mock implementations to verify behavior without requiring full runtime environments or network transports.
Architecture Overview: Unit tests instantiate mock implementations that satisfy core trait interfaces (RpcServiceCallerInterface) without requiring actual network connections or async runtimes. Mock implementations use synchronization primitives (Arc<Mutex>, Arc<AtomicBool>) to coordinate test behavior and response injection. Tests verify both success paths and error handling using pattern matching against RpcServiceError types.
graph TB
subgraph "Test Layer"
TEST["Test Functions\n(#[tokio::test])"]
MOCK["MockRpcClient"]
ASSERTIONS["Test Assertions\nassert_eq!, match patterns"]
end
subgraph "Component Under Test"
INTERFACE["RpcServiceCallerInterface\ntrait implementation"]
DISPATCHER["RpcDispatcher"]
CALL["call_rpc_buffered\ncall_rpc_streaming"]
end
subgraph "Supporting Test Infrastructure"
CHANNELS["DynamicSender/Receiver\nmpsc::unbounded, mpsc::channel"]
ENCODER["RpcStreamEncoder\ndummy instances"]
ATOMIC["Arc<AtomicBool>\nconnection state"]
MUTEX["Arc<Mutex<Option<DynamicSender>>>\nresponse coordination"]
end
TEST --> MOCK
MOCK -.implements.-> INTERFACE
MOCK --> CHANNELS
MOCK --> ENCODER
MOCK --> ATOMIC
MOCK --> MUTEX
TEST --> CALL
CALL --> DISPATCHER
TEST --> ASSERTIONS
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:20-93 tests/rpc_dispatcher_tests.rs:30-39
Mock Implementation Pattern
Unit tests use mock implementations of core interfaces to isolate component behavior. The primary mock pattern implements RpcServiceCallerInterface with controllable behavior.
MockRpcClient Structure
| Component | Type | Purpose |
|---|---|---|
response_sender_provider | Arc<Mutex<Option<DynamicSender>>> | Shared reference to response channel sender for injecting test responses |
is_connected_atomic | Arc<AtomicBool> | Atomic flag controlling connection state returned by is_connected() |
get_dispatcher() | Returns Arc<TokioMutex<RpcDispatcher>> | Provides fresh dispatcher instance for each test |
get_emit_fn() | Returns Arc<dyn Fn(Vec<u8>)> | No-op emit function (network writes not needed) |
call_rpc_streaming() | Returns (RpcStreamEncoder, DynamicReceiver) | Creates test channels and stores sender for response injection |
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:22-93
Mock Implementation Details
The MockRpcClient struct implements the RpcServiceCallerInterface trait with minimal functionality required for unit testing:
Key Implementation Characteristics:
graph LR
subgraph "MockRpcClient Implementation"
MOCK["MockRpcClient"]
PROVIDER["response_sender_provider\nArc<Mutex<Option<DynamicSender>>>"]
CONNECTED["is_connected_atomic\nArc<AtomicBool>"]
end
subgraph "Trait Methods"
GET_DISP["get_dispatcher()\nreturns new RpcDispatcher"]
GET_EMIT["get_emit_fn()\nreturns no-op closure"]
IS_CONN["is_connected()\nreads atomic bool"]
CALL_STREAM["call_rpc_streaming()\ncreates channels, stores sender"]
end
MOCK --> PROVIDER
MOCK --> CONNECTED
MOCK -.implements.-> GET_DISP
MOCK -.implements.-> GET_EMIT
MOCK -.implements.-> IS_CONN
MOCK -.implements.-> CALL_STREAM
CALL_STREAM --> PROVIDER
IS_CONN --> CONNECTED
-
Fresh Dispatcher: Each call to
get_dispatcher()returns a newRpcDispatcherinstance wrapped inArc<TokioMutex>extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:32-34 -
No-op Emit: The
get_emit_fn()returns a closure that discards bytes, as network transmission is not needed in unit tests extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:36-38 -
Channel Creation:
call_rpc_streaming()creates either bounded or unbounded channels based onDynamicChannelType, stores the sender in shared state for later response injection, and returns a dummyRpcStreamEncoderextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:44-85 -
Connection State Control: Tests control the
is_connected()return value by modifying theAtomicBoolextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:40-42
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:24-93
Unit Test Structure and Patterns
Prebuffered RPC Call Tests
Tests for prebuffered RPC calls follow a standard pattern: instantiate mock client, spawn response injection task, invoke RPC method, verify result.
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133
sequenceDiagram
participant Test as "Test Function"
participant Mock as "MockRpcClient"
participant Task as "tokio::spawn\nresponse task"
participant Sender as "DynamicSender"
participant Call as "call_rpc_buffered"
participant Result as "Test Assertion"
Test->>Mock: Instantiate with Arc<Mutex<Option<DynamicSender>>>
Test->>Task: spawn background task
Test->>Call: invoke with RpcRequest
Call->>Mock: call_rpc_streaming()
Mock->>Mock: create channels
Mock->>Sender: store sender in Arc<Mutex>
Mock-->>Call: return (encoder, receiver)
Task->>Sender: poll for sender availability
Task->>Sender: send_and_ignore(Ok(response_bytes))
Call->>Call: await response from receiver
Call-->>Test: return (encoder, Result<T>)
Test->>Result: assert_eq! or match pattern
Test Case: Successful Buffered Call
The test_buffered_call_success function demonstrates the success path for prebuffered RPC calls:
Test Setup:
- Creates
MockRpcClientwithis_connectedset totrueextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:99-105 - Defines echo payload
b"hello world".to_vec()and decode function extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:107-108
Response Injection:
- Spawns background task that polls
sender_provideruntil sender becomes available extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:110-121 - Sends
Ok(echo_payload)through the channel usingsend_and_ignore()extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs119
Invocation:
- Constructs
RpcRequestwithEcho::METHOD_ID, param bytes, andis_finalized: trueextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:123-128 - Calls
client.call_rpc_buffered(request, decode_fn).awaitextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs130
Assertion:
- Verifies returned result equals original echo payload extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs132
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:97-133
Test Case: Remote Error Handling
The test_buffered_call_remote_error function verifies error propagation from service handlers:
Error Injection:
- Background task sends
Err(RpcServiceError::Rpc(RpcServiceErrorPayload))with codeRpcServiceErrorCode::Failand message"item does not exist"extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:155-158
Error Verification:
- Uses pattern matching against
Err(RpcServiceError::Rpc(err))extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:170-176 - Asserts error code matches
RpcServiceErrorCode::Failextensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs172 - Asserts error message equals expected string extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs173
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:135-177
Test Case: Trait-Level Error Conversion
The test_prebuffered_trait_converts_error function verifies that the RpcMethodPrebuffered trait correctly propagates service errors:
Trait Invocation:
- Calls
Echo::call(&client, b"some input".to_vec()).awaitdirectly on trait method extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs203 - This tests the higher-level trait abstraction rather than the lower-level
call_rpc_buffered
Error Verification:
- Verifies system error with code
RpcServiceErrorCode::Systemand message"Method has panicked"extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:205-211
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:179-212
graph TB
subgraph "Test Setup"
TEST["rpc_dispatcher_call_and_echo_response"]
OUTBUF["outgoing_buf\nRc<RefCell<Vec<u8>>>"]
end
subgraph "Client Dispatcher"
CLIENT_DISP["client_dispatcher\nRpcDispatcher::new()"]
CLIENT_CALL["dispatcher.call()\nwith emit closure"]
CLIENT_STREAM["RpcStreamEvent handler\nreceives responses"]
end
subgraph "Server Dispatcher"
SERVER_DISP["server_dispatcher\nRpcDispatcher::new()"]
SERVER_READ["dispatcher.read_bytes()\nreturns request IDs"]
SERVER_DELETE["dispatcher.delete_rpc_request()\nretrieves buffered request"]
SERVER_RESPOND["dispatcher.respond()\nemits response frames"]
end
subgraph "Method Handlers"
ADD_HANDLER["ADD_METHOD_ID\nsum numbers"]
MULT_HANDLER["MULT_METHOD_ID\nmultiply numbers"]
end
TEST --> CLIENT_DISP
TEST --> SERVER_DISP
TEST --> OUTBUF
CLIENT_CALL --> OUTBUF
OUTBUF --> SERVER_READ
SERVER_READ --> SERVER_DELETE
SERVER_DELETE --> ADD_HANDLER
SERVER_DELETE --> MULT_HANDLER
ADD_HANDLER --> SERVER_RESPOND
MULT_HANDLER --> SERVER_RESPOND
SERVER_RESPOND --> CLIENT_STREAM
RPC Dispatcher Unit Tests
The RpcDispatcher component is tested using a client-server pair of dispatchers that communicate through shared buffers, simulating network transmission without actual I/O.
Test Architecture: Dispatcher Echo Test
Sources: tests/rpc_dispatcher_tests.rs:30-203
Dispatcher Test Flow
The rpc_dispatcher_call_and_echo_response test demonstrates the complete request-response cycle at the dispatcher level:
Phase 1: Request Encoding and Transmission
| Step | Component | Action |
|---|---|---|
| 1 | Test | Creates AddRequestParams and MultRequestParams structs tests/rpc_dispatcher_tests.rs:10-28 |
| 2 | Test | Encodes parameters using bitcode::encode() tests/rpc_dispatcher_tests.rs:44-46 |
| 3 | Test | Constructs RpcRequest with method ID, param bytes, is_finalized: true tests/rpc_dispatcher_tests.rs:42-49 |
| 4 | Client Dispatcher | Calls dispatcher.call() with chunk size 4, emit closure that writes to outgoing_buf, and stream event handler tests/rpc_dispatcher_tests.rs:74-122 |
| 5 | Emit Closure | Extends outgoing_buf with emitted bytes tests/rpc_dispatcher_tests.rs:80-82 |
Phase 2: Request Reception and Processing
| Step | Component | Action |
|---|---|---|
| 6 | Test | Chunks outgoing_buf into 4-byte segments tests/rpc_dispatcher_tests.rs:127-129 |
| 7 | Server Dispatcher | Calls dispatcher.read_bytes(chunk) returning RPC request IDs tests/rpc_dispatcher_tests.rs:130-132 |
| 8 | Server Dispatcher | Checks is_rpc_request_finalized() for each request ID tests/rpc_dispatcher_tests.rs:135-142 |
| 9 | Server Dispatcher | Calls delete_rpc_request() to retrieve and remove finalized request tests/rpc_dispatcher_tests.rs144 |
| 10 | Test | Decodes param bytes using bitcode::decode() tests/rpc_dispatcher_tests.rs:152-153 |
Phase 3: Response Generation and Transmission
| Step | Component | Action |
|---|---|---|
| 11 | Method Handler | Processes request (sum for ADD, product for MULT) tests/rpc_dispatcher_tests.rs:151-187 |
| 12 | Test | Encodes response using bitcode::encode() tests/rpc_dispatcher_tests.rs:157-159 |
| 13 | Test | Constructs RpcResponse with rpc_request_id, method ID, status, and payload tests/rpc_dispatcher_tests.rs:161-167 |
| 14 | Server Dispatcher | Calls dispatcher.respond() with chunk size 4 and emit closure tests/rpc_dispatcher_tests.rs:193-197 |
| 15 | Emit Closure | Calls client_dispatcher.read_bytes() directly tests/rpc_dispatcher_tests.rs195 |
| 16 | Client Stream Handler | Receives RpcStreamEvent::Header and RpcStreamEvent::PayloadChunk tests/rpc_dispatcher_tests.rs:86-118 |
| 17 | Client Stream Handler | Decodes response bytes and logs results tests/rpc_dispatcher_tests.rs:102-112 |
Sources: tests/rpc_dispatcher_tests.rs:30-203
classDiagram
class AddRequestParams {+Vec~f64~ numbers}
class MultRequestParams {+Vec~f64~ numbers}
class AddResponseParams {+f64 result}
class MultResponseParams {+f64 result}
AddRequestParams ..|> Encode
AddRequestParams ..|> Decode
MultRequestParams ..|> Encode
MultRequestParams ..|> Decode
AddResponseParams ..|> Encode
AddResponseParams ..|> Decode
MultResponseParams ..|> Encode
MultResponseParams ..|> Decode
Request and Response Types
The dispatcher test defines custom request and response types that demonstrate the serialization pattern:
Request Types:
All types derive Encode and Decode from the bitcode crate, enabling efficient binary serialization tests/rpc_dispatcher_tests.rs:10-28
Method ID Constants:
ADD_METHOD_ID: u64 = 0x01tests/rpc_dispatcher_tests.rs7MULT_METHOD_ID: u64 = 0x02tests/rpc_dispatcher_tests.rs8
Sources: tests/rpc_dispatcher_tests.rs:7-28
Test Coverage Areas
Components Under Test
| Component | Test File | Key Aspects Verified |
|---|---|---|
RpcDispatcher | tests/rpc_dispatcher_tests.rs | Request correlation, chunked I/O, response routing, finalization detection |
RpcServiceCallerInterface | extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs | Prebuffered call success, error propagation, trait-level invocation |
call_rpc_buffered | extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs | Response deserialization, error handling, channel coordination |
RpcMethodPrebuffered trait | extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs | Trait method invocation, error conversion |
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:1-213 tests/rpc_dispatcher_tests.rs:1-204
Assertion Patterns
Equality Assertions:
assert_eq!(result.unwrap(), echo_payload)- Verifies successful response matches expected data extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs132assert_eq!(rpc_header.rpc_method_id, rpc_method_id)- Validates header correlation tests/rpc_dispatcher_tests.rs91
Pattern Matching:
match result {
Err(RpcServiceError::Rpc(err)) => {
assert_eq!(err.code, RpcServiceErrorCode::Fail);
assert_eq!(err.message, "item does not exist");
}
_ => panic!("Expected a RemoteError"),
}
extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:170-176
Boolean Assertions:
assert!(result.is_err())- Verifies error return extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs205
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs:132-211 tests/rpc_dispatcher_tests.rs91
Running Unit Tests
Unit tests use the standard Rust test infrastructure with async support via tokio::test:
Test Attributes:
#[tokio::test]- Enables async test execution with Tokio runtime extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs97#[test]- Standard synchronous test (used for non-async dispatcher tests) tests/rpc_dispatcher_tests.rs30#[instrument]- Optional tracing instrumentation for debugging tests/rpc_dispatcher_tests.rs31
Running Tests:
Test Organization:
Unit tests are located in two primary locations:
- Workspace-level tests:
tests/directory at repository root contains core component tests tests/rpc_dispatcher_tests.rs1 - Package-level tests:
extensions/<package>/tests/directories contain extension-specific tests extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs1
Sources: extensions/muxio-rpc-service-caller/tests/prebuffered_caller_tests.rs97 tests/rpc_dispatcher_tests.rs:30-31