Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Testing  ›  Capstone Project

Mocking and Property-Based Testing

Mocking & Property Based Testing 18 minA trait lets tests swap a slow or unpredictable dependency for a fake one, and property-based tests check a rule against many generated inputs instead of a few chosen examples.

Mocking and Property-Based Testing

Two kinds of test are hard to write by hand. One depends on something you do not control, like today's date, so it passes on Monday and fails on Friday. The other has so many possible inputs that three examples leave huge gaps. Rust handles the first with traits and the second with generated inputs.

The rule

Swap what you can't control through a trait; test rules, not just examples.

Mocking through a trait. Code that calls the system clock directly cannot be tested reliably. Put the clock behind a trait, and let the caller choose:

pub trait Clock {
    fn today(&self) -> u32;          // day number
}

pub fn is_overdue(due_day: u32, clock: &dyn Clock) -> bool {
    clock.today() > due_day
}

struct FixedClock(u32);              // test double
impl Clock for FixedClock {
    fn today(&self) -> u32 { self.0 }
}

#[test]
fn overdue_after_due_day() {
    assert!(is_overdue(21, &FixedClock(30)));
    assert!(!is_overdue(21, &FixedClock(21)));
}

The real program passes a SystemClock that reads the actual date; tests pass a fake that always says the same thing. A generic parameter, fn is_overdue<C: Clock>(due_day: u32, clock: &C), works the same way. The trait is the seam where a stand-in fits. Crates such as mockall can generate these stand-ins, including checks like "was today called exactly once?", but a hand-written fake is often enough.

Property-based testing. Instead of choosing inputs, state a rule that must hold for all inputs, and let a tool generate hundreds of them. With the proptest crate (a dev-dependency):

use proptest::prelude::*;

proptest! {
    #[test]
    fn fee_never_exceeds_cap(days in 0u32..10_000) {
        prop_assert!(late_fee(days) <= 200);
    }

    #[test]
    fn fee_never_goes_down(days in 0u32..10_000) {
        prop_assert!(late_fee(days + 1) >= late_fee(days));
    }
}

When a generated input breaks the rule, proptest shrinks it: it searches for the smallest input that still fails and reports that one, so you debug days = 21, not days = 8_734. quickcheck is another crate built on the same idea.

The shortcut

Ask two questions of any function: "What does it depend on that I can't control?" → put that behind a trait. "What must always be true of its output?" → write that as a property.

Testing a renewal ruleStep 1 of 4
  1. The function

    renew(due_day, clock) adds 14 days to the due day, but only if the book is not already overdue.

The trap is a property that just repeats the code, like "fee equals days times 10, capped at 200". Good properties are simpler, independent facts: bounds, order, round trips.

Drills

Answer fast — trust the rule.

quick drill

Why put the clock behind a Clock trait?

quick drill

What does a property-based test check?

quick drill

A property fails for days = 8734. What does shrinking do?

quick drill

Following the lesson's advice, which is the best property to test for late_fee?

Remember

  • Put what you cannot control behind a trait, and pass a fake in tests.
  • Property tests check a rule over many generated inputs and shrink failures.
  • Trap: a property that just copies the implementation.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainTest the due-date logic

is_overdue reads today's date from the system clock, so its tests give different results on different days. Explain how a trait lets you test it reliably, and write one property that late_fee should satisfy for every input.