Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Test code that reads the clock or uses the network

Use when: a function asks the outside world for something, such as today's date or a message sent, so a direct test would change from day to day or need a real service

Goal: the function taking its outside needs as traits, and fast tests that pass it a stub, a recording mock or a failing double

Taught in Mocking with traits

  1. 1

    List each thing the function asks from outside the program: the time, the network, a payment service.

    Example

    send_reminders needs two things: today's date, and a way to send a text message.

    Why: these are what make the result change between runs, or make a test touch a real service.

  2. 2

    For each one, write a trait with only the methods the function calls.

    Example

    trait Clock { fn today(&self) -> u32; }, and a Messenger trait with one method, send.

    Why: every stand-in must write out every method, so a small trait keeps each one short.

  3. 3

    Change the function's parameters to &impl Trait, or &dyn Trait. Then implement the trait for the real type.

    Example

    send_reminders(debts: &[Debt], clock: &impl Clock, messenger: &mut impl Messenger). SystemClock and GatewayMessenger implement the traits for the real program.

    • callers pass one concrete type — &impl Clock.
    • callers keep their clocks as Box<dyn Clock> — &dyn Clock.

    Why: a function that never names the real type accepts any type with the trait, including one written for a test.

  4. 4

    In the test module, write a small type for each situation you want to test.

    Example

    RecordingMessenger pushes (to, text) onto self.sent and returns Ok(()). OfflineMessenger always returns Err(SendError::Offline).

    • the test needs a fixed answer — a stub, such as FixedClock(101).
    • the test checks what was sent — a recording mock that pushes each call onto a Vec.
    • the test checks the error path — a double whose method always returns Err.

    Why: each double sets up one situation the same way on every run, with no network.

  5. 5

    Call the function with the doubles. Pick stub values on both sides of each rule's edge. Assert on the result, and on what the mock recorded.

    Example

    Bo's dinner was on day 98. FixedClock(100) gives 0 reminders, since that's only 2 days. FixedClock(101) gives 2. Then messenger.sent must hold both messages, word for word.

    Why: the edge is where > written in place of >= shows up, and the record proves what left the program.

Common mistakes

  • Copying all of a big client's methods into the trait. Only the ones the code calls belong there.
  • Building the real GatewayMessenger inside the function. Then no test can swap it out.
  • Checking only the returned count, not what the mock recorded.
  • Giving send a &self receiver, then trying to push onto a Vec inside it.