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
Quick recipe
-
1
List each thing the function asks from outside the program: the time, the network, a payment service.
Example
send_remindersneeds 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
For each one, write a trait with only the methods the function calls.
Example
trait Clock { fn today(&self) -> u32; }, and aMessengertrait with one method,send.Why: every stand-in must write out every method, so a small trait keeps each one short.
-
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).SystemClockandGatewayMessengerimplement 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.
- callers pass one concrete type —
-
4
In the test module, write a small type for each situation you want to test.
Example
RecordingMessengerpushes(to, text)ontoself.sentand returnsOk(()).OfflineMessengeralways returnsErr(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.
- the test needs a fixed answer — a stub, such as
-
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. Thenmessenger.sentmust 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
GatewayMessengerinside the function. Then no test can swap it out. - Checking only the returned count, not what the mock recorded.
- Giving
senda&selfreceiver, then trying to push onto aVecinside it.