pub trait InProcess: Broker<Connected: TestableBroker> {
// Required method
fn connect_in_process(
self,
) -> impl Future<Output = Result<Self::Connected, Self::Error>> + Send;
}Expand description
A broker that runs inside the test process.
connect_in_process is the consuming transition a test takes
instead of connect: it produces the broker’s own connected form, backed
by an in-process transport, with no I/O. The connected form is the one production gets, so the
app’s routes, publish policies and subscription descriptors resolve against it unchanged, and
TestApp runs the service’s production app as it is.
The transport has no configuration of its own. It reads every setting from the broker it was built from (default group, prefetch, topology declaration, whatever the real broker applies), so a test cannot run against settings production does not have. It never succeeds where the real broker fails: a publish, a settlement or a subscription the server would reject is rejected in process the same way.
A broker crate implements this under its testing feature, next to
TestableBroker on the connected form, and registers the broker with
register_testable_broker!.
MemoryBroker is the reference: it has no server, so its
in-process transition is its ordinary connect.
The in-process mode lives in the production types: the connected form, its subscriber, its
publishers and its delivery each gain an in-process variant that exists only under the
testing feature. Without the feature there is one variant and no branch, and a service’s
production build is unchanged, because Cargo does not enable a dev-dependency’s features in a
production binary. A mount that compiles against the broker compiles in process, and a
capability is the same type in both.
The same test also runs against a stand: TestApp::start_live
connects the broker through its ordinary connect and needs nothing beyond the connected form
and its DefaultPublish policy, through which the harness publishes a test’s input.
§Writing one you can trust
A service’s whole test suite runs against this transport, so every difference from the real one is a green test for behaviour production does not have.
- Run the
conformancesuites in process, not only against a server:run_suite, thenlifecycleand the capability suites throughInProcessBroker.lifecycleasks what an in-process transport is rarely asked: whether a publisher created before the shutdown errors afterwards. A transport whose publish is a channel send accepts the message instead. - Settle the way the transport settles. Where the real
ackanswersAckError::Unsupported, the in-process one answers the same;Ok(())there is how a handler returningretry()passes in a test and loses the message in production. - Reproduce what the client does; do not fake what the broker does. Competing consumers, group distribution, correlation, reply routing and buffering until commit are client-side or routing-level, and an in-process copy of them is exact. Cluster atomicity, fencing, broker-held timeouts and exactly-once are broker-side, and the live mode covers them. Competing consumers matter most: handing every message of a queue to every subscriber of it is a fan-out, and two workers sharing the queue then each run the whole stream with no error anywhere.
- Keep what the real broker keeps. A queue or a log that exists before the subscription
delivers what reached it earlier; declare that with
TestableBroker::backlog, and read a setting that decides it (an earliest offset, a deliver-all consumer) off the connected form.backlog_matches_serverchecks the declaration against the server. - Read the production wiring. A check the real broker makes at subscribe or publish time (a declaration it refuses, a name too long for the protocol, a header it cannot carry) runs in process through the same code.
- Give every gap a comment naming the assertion it makes unsound and what covers it instead, and pin the gap with a test asserting the handler is not reached, which fails the day someone routes what the transport deliberately does not.
// No fencing: a second producer claiming the same transactional id is not rejected here, so a
// test cannot assert the first one is fenced out. `capabilities::transactions` against a real
// server is what covers that.§Examples
use std::future::Future;
use ruststream::Broker;
use ruststream::memory::{ConnectedMemoryBroker, MemoryBroker, MemoryError};
use ruststream::testing::InProcess;
/// A broker crate built over the in-memory bus: its connected form is the bus's own, so its
/// in-process transition is the bus's too.
pub struct EdgeBroker(MemoryBroker);
impl Broker for EdgeBroker {
type Error = MemoryError;
type Connected = ConnectedMemoryBroker;
fn connect(
self,
) -> impl Future<Output = Result<ConnectedMemoryBroker, MemoryError>> + Send {
self.0.connect()
}
}
impl InProcess for EdgeBroker {
fn connect_in_process(
self,
) -> impl Future<Output = Result<ConnectedMemoryBroker, MemoryError>> + Send {
self.0.connect_in_process()
}
}
// Under the crate's `testing` feature: `TestApp::start` now runs an app on `EdgeBroker`.
ruststream::register_testable_broker!(EdgeBroker);Required Methods§
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl<Log: LogMode> InProcess for MemoryBroker<Log>
testing only.