DEV Community

Tom for localhost.run

Posted on

Mocking with Rust

I've got something cooking that i've written in Rust and I recently discovered a neat trick to test it.

Coming from python, mocks are a thing i often reach for. I find it really neat that I can throw an api from a package in a test harness so easily, same as how I'd do with a HTTP api in an integration test.

Something external I often mock is time. That's not some deep nihilist philosophy, i mean literally if there's a delay i'll mock out stuff that waits, sleep, etc so that my tests can prove the delayed behaviour instantly.

In the Rust thing I'm working on I faced this exact issue, specifically a timeout on a socket.
I have a check on an Instant, is .elapsed() greater than a Duration, and if it is I drop the socket from the hashmap it's stored in and it gets closed.

But I don't want my tests to wait the actual time, I want to be able to manipulate that time.

In python I would move the external world of the time api around the timeout internal to my function, but in Rust I can use an impl inside my tests to flip that and just move my timeout internal to my struct around the external world. check this out:

struct WaitingPlace {
    last_switch: Instant,
    switch_every: Duration,
    is_on: bool,
}
impl WaitingPlace {
    fn new(switch_every: Duration) -> Self {
        Self {
            last_switch: Instant::now(),
            switch_every,
            is_on: false,
        }
    }
    fn do_stuff(&mut self) {
        self.flip_check();
        println!("is_on: {}", self.is_on);
    }
    fn flip_check(&mut self) {
        if self.last_switch.elapsed() > self.switch_every {
            self.last_switch = Instant::now();
            self.is_on = !self.is_on;
        }
    }
}
#[cfg(test)]
mod tests {
    fn test_time() {
        use super::WaitingPlace;
        impl WaitingPlace {  // <- here's the magic where I reach into the struct and change the time
            fn move_time(&mut self, last_switch: Instant) {
                self.last_switch = last_switch;
            }
        }

        let mut wp = WaitingPlace::new(Duration::new(1, 0));
        assert_eq!(wp.is_on, false);
        wp.do_stuff();
        assert_eq!(wp.is_on, false);
        wp.move_time(Instant::now() - Duration::new(1, 0));
        wp.do_stuff();
        assert_eq!(wp.is_on, true);
    }
}
Enter fullscreen mode Exit fullscreen mode

it's effectively a window onto the internals of my WaitingPlace struct that only exists in my tests. pretty neat i reckon. Maybe this is super obvious to you seasoned rust devs, or maybe i've created an abomination that no one wants to see. Either way i thought it was neat.

If you know a better way of doing this let me know.

Top comments (0)