rtb-test-support¶
rtb-test-support is the promoted test-side constructor for
[rtb_app::App]. It provides TestAppBuilder
gated behind a crate-private sealed trait whose only implementor is
TestWitness. Downstream crates depend on this
from [dev-dependencies] to get a consistent test-helper API.
Overview¶
Production App construction goes through
rtb_cli::Application::builder — which also installs logging,
miette hooks, panic hooks, signal handlers, and command
registration. Bypassing that pipeline in production is a hazard;
forgetting a hook install silently swallows errors or skips
cancellation propagation.
For unit and integration tests, however, a full Application is
overkill. rtb-test-support is the bypass path — opt-in via
[dev-dependencies], signalled in Cargo.toml, visible to audit.
Design rationale¶
- Sealed-trait
TestWitness. The bypass builder takes a value of a crate-privateSealedtrait. Only this crate can construct aTestWitness. Downstream crates that depend onrtb-appbut not onrtb-test-supportcannot call the bypass builder. [dev-dependencies]placement. Production binaries that only depend onrtb-app+rtb-clido not compilertb-test-supportin, and cannot reachTestAppBuilderat all.- Honest caveat (partly closed in 0.4.1).
rtb_app::Apphadpubfields at v0.1, so any crate depending onrtb-appcould construct anAppvia struct literal directly. Since 0.4.1 the type-erasedconfigandtyped_config_opsfields arepub(crate)(per the v0.4.1 scope addendum, A2 resolution); the remainingpubfields (metadata / version / assets / shutdown / credentials_provider) stay public for callsite ergonomics. The seal is still primarily a speed-bump + visibility signal rather than watertight access control.
Core types¶
TestWitness¶
pub struct TestWitness(());
impl TestWitness {
pub const fn new() -> Self;
}
// Sealed: only rtb-test-support implements `sealed::Sealed` for it.
TestAppBuilder¶
#[must_use]
pub struct TestAppBuilder<W: sealed::Sealed> { /* ... */ }
impl TestAppBuilder<TestWitness> {
pub const fn new(witness: TestWitness) -> Self;
pub fn tool(self, name: &str, version: &str) -> Self; // name + semver string
pub fn metadata(self, m: ToolMetadata) -> Self; // override just metadata
pub fn version(self, v: VersionInfo) -> Self; // override just version
// Typed-config wiring (since 0.4.1). `config` matches the
// production `Application::builder().config<C>(...)` for full
// fidelity; `config_value` is the ergonomic shortcut that
// wraps `c` in `Config::<C>::with_value(c)`.
pub fn config<C>(self, config: Config<C>) -> Self
where C: Serialize + DeserializeOwned + JsonSchema + Send + Sync + 'static;
pub fn config_value<C>(self, c: C) -> Self
where C: Serialize + DeserializeOwned + JsonSchema + Send + Sync + 'static;
pub fn build(self) -> App; // panics on missing required
}
API surface¶
| Item | Kind | Since |
|---|---|---|
TestWitness |
struct | 0.1.0 |
TestAppBuilder<W> |
struct (generic, sealed) | 0.1.0 |
TestAppBuilder::{new, tool, metadata, version, build} |
methods | 0.1.0 |
TestAppBuilder::{config, config_value} |
methods (typed-config wiring) | 0.4.1 |
Usage¶
In a downstream crate's Cargo.toml:
In a test:
use rtb_test_support::{TestAppBuilder, TestWitness};
#[tokio::test]
async fn my_test() {
let app = TestAppBuilder::new(TestWitness::new())
.tool("mytool", "1.2.3")
.build();
// `app` has default (empty) config, default (empty) assets,
// a fresh shutdown CancellationToken.
let result = my_command.run(app).await;
assert!(result.is_ok());
}
Relationship to App::for_testing¶
rtb_app::App::for_testing is the existing #[doc(hidden)] pub fn
helper used by tests within rtb-app itself. It remains in place
for those internal tests. New downstream-crate tests should use
rtb-test-support's TestAppBuilder — it's the promoted, more
ergonomic path and its sealed-trait signature is the clearer
indicator of test-only intent.
Post-0.1 work:
- Make
App's fieldspub(crate)+ accessor methods. - Remove
App::for_testingin favour ofTestAppBuilderexclusively. - At that point the seal becomes actual access control.
Testing¶
2 acceptance criteria:
builder_produces_an_app—TestAppBuilder::new().tool("mytool", "1.2.3").build()yields a validAppwith the expected metadata and version.child_token_cancellation_cascades— cancellingapp.shutdowncancels tokens derived viaapp.shutdown.child_token().
Spec and status¶
- Status:
IMPLEMENTEDsince 0.1.0 (added post-v0.1 review). - Source:
crates/rtb-test-support/.
Related¶
- rtb-app — where
App::for_testinglives. - Engineering Standards §2.7
— why the
#[doc(hidden)] pubpattern is a smell and what sealed-trait replaces.