Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Random but leave Render pending;
  • 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.