๐Ÿฆ€ Functional Rust
๐ŸŽฌ Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Traits define shared behaviour โ€” like interfaces but with default implementations

โ€ข Generics with trait bounds: fn process(item: T) โ€” monomorphized at compile time

โ€ข Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

โ€ข Blanket implementations apply traits to all types matching a bound

โ€ข Associated types and supertraits enable complex type relationships

751: Mocking via Traits: Test Doubles in Rust

Difficulty: 3 Level: Advanced Create a test double by implementing a trait differently for tests โ€” no mocking framework needed, because traits are the interface.

The Problem This Solves

In production code, your `UserService` sends real emails via SMTP. In tests, you don't want to hit a real mail server โ€” that's slow, fragile, and sends actual emails to real people. You need a way to substitute a fake implementation that records what was sent so you can assert on it. In Python and JavaScript, mocking frameworks like `unittest.mock` or Jest's `jest.fn()` work by patching function references at runtime. Rust's type system makes runtime patching impossible โ€” but it makes a better approach obvious: define the dependency as a trait, and implement that trait differently in tests. This pattern also forces good design. If you can't easily swap out a dependency, it's probably too tightly coupled. Designing around a `Sender` trait rather than a concrete `SmtpSender` struct makes your business logic more testable and more flexible.

The Intuition

Think of a trait as a Java interface. Your `UserService<S: EmailSender>` accepts any type that can send email. In production you pass `SmtpSender`. In tests you pass `MockEmailSender`, which records all calls in a `Vec` instead of hitting a network. Unlike Jest's `jest.fn()`, there's no global registry or monkey-patching. The compiler verifies at compile time that your mock satisfies the same contract as the real implementation. If you add a method to the `EmailSender` trait, the mock fails to compile until you implement it. `RefCell<Vec<SentEmail>>` lets the mock record sent emails even when the `send` method only has `&self` (shared reference). This is interior mutability โ€” a common pattern in Rust test doubles.

How It Works in Rust

// The interface โ€” both real and mock implement this
pub trait EmailSender {
 fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String>;
}

// Production: hits a real SMTP server
pub struct SmtpSender { pub host: String, pub port: u16 }

impl EmailSender for SmtpSender {
 fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
     // ... real SMTP connection
     Ok(())
 }
}

// Test double: records calls, can be configured to fail
pub struct MockEmailSender {
 sent: RefCell<Vec<SentEmail>>,  // interior mutability โ€” &self can still record
 should_fail: bool,
}

impl EmailSender for MockEmailSender {
 fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
     if self.should_fail { return Err("mock SMTP failure".into()); }
     self.sent.borrow_mut().push(SentEmail {
         to: to.into(), subject: subject.into(), body: body.into(),
     });
     Ok(())
 }
}

// Business logic โ€” generic over any EmailSender
pub struct UserService<S: EmailSender> { sender: S }

impl<S: EmailSender> UserService<S> {
 pub fn welcome_user(&self, email: &str, name: &str) -> Result<(), String> {
     self.sender.send(email, "Welcome!", &format!("Hi {name}, welcome!"))
 }
}

// In tests โ€” inject the mock
#[test]
fn welcome_user_sends_to_correct_address() {
 let mock = MockEmailSender::new();
 let svc = UserService::new(mock);
 svc.welcome_user("alice@example.com", "Alice").unwrap();

 let sent = svc.sender.get_sent();
 assert_eq!(sent.len(), 1);
 assert_eq!(sent[0].to, "alice@example.com");
 assert!(sent[0].body.contains("Alice"));
}

#[test]
fn error_from_sender_propagates() {
 let mock = MockEmailSender::failing();  // configured to fail
 let svc = UserService::new(mock);
 assert!(svc.welcome_user("x@y.com", "X").is_err());
}
Key points:

What This Unlocks

Key Differences

ConceptOCamlRust
Mocking approachFirst-class functions or modulesTrait implementations โ€” no framework
Runtime patchingPossible via mutable referencesImpossible by design; use generics
Interior mutabilityReferences are mutable`RefCell<T>` for `&self` mutation
Compile-time safetyOCaml module typesGeneric bound `S: EmailSender` verified at compile time
Failure injectionPass a different functionConstruct mock with `MockEmailSender::failing()`
External mock libraryN/A`mockall` crate for auto-generated mocks
/// 751: Mocking via Traits โ€” test doubles without external crates

use std::cell::RefCell;

// โ”€โ”€ The dependency trait โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub trait EmailSender {
    fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String>;
}

// โ”€โ”€ Real implementation (production) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub struct SmtpSender {
    pub host: String,
    pub port: u16,
}

impl EmailSender for SmtpSender {
    fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
        println!("[SMTP {}:{}] To={} Subject={} Body={}",
            self.host, self.port, to, subject, &body[..body.len().min(50)]);
        Ok(())
    }
}

// โ”€โ”€ Mock implementation (tests) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[derive(Debug, Clone)]
pub struct SentEmail {
    pub to:      String,
    pub subject: String,
    pub body:    String,
}

pub struct MockEmailSender {
    sent:        RefCell<Vec<SentEmail>>,
    should_fail: bool,
}

impl MockEmailSender {
    pub fn new() -> Self {
        MockEmailSender { sent: RefCell::new(Vec::new()), should_fail: false }
    }

    pub fn failing() -> Self {
        MockEmailSender { sent: RefCell::new(Vec::new()), should_fail: true }
    }

    pub fn sent_count(&self) -> usize {
        self.sent.borrow().len()
    }

    pub fn get_sent(&self) -> std::cell::Ref<Vec<SentEmail>> {
        self.sent.borrow()
    }

    pub fn last_sent(&self) -> Option<SentEmail> {
        self.sent.borrow().last().cloned()
    }
}

impl EmailSender for MockEmailSender {
    fn send(&self, to: &str, subject: &str, body: &str) -> Result<(), String> {
        if self.should_fail {
            return Err("mock SMTP failure".to_owned());
        }
        self.sent.borrow_mut().push(SentEmail {
            to:      to.to_owned(),
            subject: subject.to_owned(),
            body:    body.to_owned(),
        });
        Ok(())
    }
}

// โ”€โ”€ Business logic (depends on EmailSender trait) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub struct UserService<S: EmailSender> {
    sender: S,
}

impl<S: EmailSender> UserService<S> {
    pub fn new(sender: S) -> Self { UserService { sender } }

    pub fn welcome_user(&self, email: &str, name: &str) -> Result<(), String> {
        self.sender.send(
            email,
            "Welcome!",
            &format!("Hi {}, welcome aboard! We're glad to have you.", name),
        )
    }

    pub fn password_reset(&self, email: &str, token: &str) -> Result<(), String> {
        self.sender.send(
            email,
            "Password Reset",
            &format!("Your reset token: {}", token),
        )
    }
}

fn main() {
    let smtp = SmtpSender { host: "smtp.example.com".into(), port: 587 };
    let svc  = UserService::new(smtp);
    svc.welcome_user("alice@example.com", "Alice").unwrap();
    svc.password_reset("alice@example.com", "RESET-TOKEN-123").unwrap();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn welcome_user_sends_email_with_correct_fields() {
        // Arrange
        let mock = MockEmailSender::new();
        let svc  = UserService::new(mock);

        // Act
        svc.welcome_user("alice@example.com", "Alice").unwrap();

        // Assert
        let sent = svc.sender.get_sent();
        assert_eq!(sent.len(), 1);
        assert_eq!(sent[0].to, "alice@example.com");
        assert_eq!(sent[0].subject, "Welcome!");
        assert!(sent[0].body.contains("Alice"));
    }

    #[test]
    fn welcome_user_sends_exactly_one_email() {
        let mock = MockEmailSender::new();
        let svc  = UserService::new(mock);
        svc.welcome_user("bob@example.com", "Bob").unwrap();
        assert_eq!(svc.sender.sent_count(), 1);
    }

    #[test]
    fn password_reset_includes_token_in_body() {
        let mock = MockEmailSender::new();
        let svc  = UserService::new(mock);
        svc.password_reset("user@example.com", "SECRET-42").unwrap();
        let email = svc.sender.last_sent().unwrap();
        assert!(email.body.contains("SECRET-42"), "token missing: {}", email.body);
    }

    #[test]
    fn failing_sender_propagates_error() {
        let mock = MockEmailSender::failing();
        let svc  = UserService::new(mock);
        let result = svc.welcome_user("user@example.com", "User");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("failure"));
    }

    #[test]
    fn multiple_calls_all_recorded() {
        let mock = MockEmailSender::new();
        let svc  = UserService::new(mock);
        svc.welcome_user("a@a.com", "A").unwrap();
        svc.welcome_user("b@b.com", "B").unwrap();
        svc.password_reset("c@c.com", "T").unwrap();
        assert_eq!(svc.sender.sent_count(), 3);
    }
}
(* 751: Mock Trait Pattern โ€” OCaml module-based mocking *)

(* Interface as module type *)
module type EMAIL_SENDER = sig
  val send : to_:string -> subject:string -> body:string -> (unit, string) result
end

(* Real implementation *)
module SmtpSender : EMAIL_SENDER = struct
  let send ~to_ ~subject ~body =
    Printf.printf "[SMTP] To: %s | Subject: %s | Body: %s\n" to_ subject body;
    Ok ()
end

(* Mock: records all calls *)
module MockSender : sig
  include EMAIL_SENDER
  val calls : unit -> (string * string * string) list
  val reset : unit -> unit
end = struct
  let recorded = ref []

  let send ~to_ ~subject ~body =
    recorded := (to_, subject, body) :: !recorded;
    Ok ()

  let calls () = List.rev !recorded
  let reset () = recorded := []
end

(* Business logic โ€” parametrized over EMAIL_SENDER *)
module UserService (Sender : EMAIL_SENDER) = struct
  let welcome_user email name =
    Sender.send
      ~to_:email
      ~subject:"Welcome!"
      ~body:(Printf.sprintf "Hi %s, welcome aboard!" name)
end

(* Tests *)
let () =
  MockSender.reset ();

  let module SUT = UserService(MockSender) in
  let _ = SUT.welcome_user "alice@example.com" "Alice" in
  let _ = SUT.welcome_user "bob@example.com" "Bob" in

  let calls = MockSender.calls () in
  assert (List.length calls = 2);
  let (to_, subj, body) = List.hd calls in
  assert (to_ = "alice@example.com");
  assert (subj = "Welcome!");
  assert (String.length body > 0);

  Printf.printf "Mock tests passed! %d call(s) recorded.\n" (List.length calls)