TEA Test Doubles
In TEA mode, the app boundary sees one AppEffect -> AppEffectOutput handler.
That handler can be a real runtime, a foreign host, or a test double.
For many tests, EffectChannel<AppEffect> is enough:
#![allow(unused)]
fn main() {
let (effects, handler) = EffectChannel::unbounded();
let app = TeaApp::new(effects);
let mut update = Box::pin(app.update(Event::Account(account::Event::StartLogin {
email: "demo@example.com".to_owned(),
password: "password".to_owned(),
})));
assert_pending!(&mut update);
handler.handle_render(async || {}).await.unwrap();
assert_ready!(&mut update);
}
The generated handler helpers let the test answer a leaf capability even though the channel carries the composed root protocol.
Why Use Test Doubles Here?
TEA effects are plain data, so it can be tempting to write a broad fake handler that returns outputs immediately. That is useful for simple tests, but it hides timing.
Channel-backed doubles let the test pause at each effect boundary:
- assert the view before an effect is answered;
- answer
Randombut leaveRenderpending; - return different values for repeated requests;
- verify that account effects and counter effects use different branches.
That makes these doubles better than ordinary mocks for async app logic. They model the boundary and the suspended computation, not only a method call count.
Native Effects Can Stay Native
Not every capability has to enter the TEA protocol.
The example TEA wrapper keeps Logger native in one constructor:
#![allow(unused)]
fn main() {
pub fn new_with_logger(handler: Arc<AppEffectHandler>, logger: Arc<dyn Logger>) -> Self
}
That is useful when logging should be handled locally while UI-visible effects
go through the protocol. krucyfiks does not require an all-or-nothing choice.