rtb-test-support¶
rtb-test-support builds an App for a test without the full rtb-cli lifecycle —
no logging setup, no miette hook install, no signal handlers. It is a separate
crate so that depending on it is a visible statement of intent in a Cargo.toml.
It shares a version line with rtb-app and is released at the same time, so the two
version numbers always match.
TestWitness¶
pub struct TestWitness(());
impl TestWitness {
pub const fn new() -> Self;
}
impl Default for TestWitness { /* delegates to new() */ }
A zero-sized value proving the caller depends on rtb-test-support. It implements a
crate-private Sealed trait, and TestAppBuilder accepts only sealed types — so a
crate that depends on rtb-app alone cannot reach the builder.
The seal is a visibility signal, not access control. See Why test construction is sealed but not locked.
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;
pub fn metadata(self, m: ToolMetadata) -> Self;
pub fn version(self, v: VersionInfo) -> Self;
pub fn config<C>(self, config: Config<C>) -> Self;
pub fn config_value<C>(self, c: C) -> Self;
pub fn build(self) -> App;
}
tool — the shortcut, and what it also sets¶
tool sets both metadata and version. The metadata it builds has
name as given and summary fixed to the literal "test"; everything else is at
its default. The version string must parse as semver — it panics with
parse test version if it does not, which is acceptable in a test and would not be
anywhere else.
Because tool writes both fields, ordering matters: calling .metadata(m) and then
.tool(...) throws the metadata away. Put tool first, or use metadata and
version and skip it.
metadata and version — the explicit route¶
let app = TestAppBuilder::new(TestWitness::new())
.metadata(ToolMetadata::builder().name("mytool").summary("real summary").build())
.version(rtb_app::version_info!())
.build();
Use these when the test cares about a metadata field tool cannot reach — a
release_source, a help channel, an update_policy.
config and config_value — wiring typed configuration¶
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;
| Method | Use it when | Equivalent to |
|---|---|---|
config_value(c) |
the test only cares about one merged value | config(Config::<C>::with_value(c)) |
config(cfg) |
the test needs layered defaults and overrides | the production Application::builder().config(...) |
Either one attaches the TypedConfigOps bundle, so after calling one of them
App::config_schema() and App::config_value() both return Some. Call neither and
both return None, and typed_config::<C>() resolves only for C = ().
Note the bounds are stricter than App::new's: JsonSchema and Serialize are
required here because the schema and the rendered value are generated eagerly.
build — and what it panics on¶
| Missing | Panic message |
|---|---|
metadata (no tool, no metadata) |
TestAppBuilder: metadata not set |
version (no tool, no version) |
TestAppBuilder: version not set |
Panicking rather than returning Result is deliberate: a test that forgot to say
which tool it is testing should stop, loudly.
What TestAppBuilder cannot set¶
These are fixed, with no builder method to change them:
Part of App |
What you get | Workaround |
|---|---|---|
assets |
Assets::default() |
none through the builder |
shutdown |
a fresh root CancellationToken |
none — derive children with child_token() after build() |
credentials_provider |
None |
assign it after build(); the field is public |
trailing_args |
empty | call App::with_trailing_args(...) on the built App |
let mut app = TestAppBuilder::new(TestWitness::new()).tool("t", "1.0.0").build();
app.credentials_provider = Some(std::sync::Arc::new(my_config));
Relationship to App::for_testing¶
rtb_app::App::for_testing is a #[doc(hidden)] pub fn used by rtb-app's own
tests. It is not cfg(test)-gated and there is no Cargo feature guarding it, so any
crate depending on rtb-app can call it. New downstream tests should use
TestAppBuilder — it is the promoted path, it can wire typed config, and its
signature makes test-only intent obvious.