Help Mocking With Traits

Hi there, I wrote this bit of code trying to unit test a function, but it's not working.

The idea is that I have an "is_morning" function that should return true if the current time is between 7am and noon, us eastern time. It makes a called to get the current system time within my trait's "now" function, and I am trying to write a next that mocks the implementation of the now function to simulate running the function at different times of the day.

I thought this code should use the mock value, but it's not... it always just uses the actual system time. :thinking:

Can anyone point out what I am doing wrong here?

Thanks!

use std::sync::{Arc, Mutex};
use chrono::{offset::LocalResult, prelude::*};

fn main() {
    println!("Hello, world!");
}

trait TimeSource {
    fn now() -> DateTime<Utc>;
}

struct DateTimeSource;

impl TimeSource for DateTimeSource {
    fn now() -> DateTime<Utc> {
        Utc::now()
    }
}

const US_EASTERN_OFFSET: u32 = 5;
const MORNING_BEGINNING_HOUR: u32 = 7; // morning starts at 7am

pub fn is_morning() -> bool {
    let utc = DateTimeSource::now();

    println!("{utc}");

    let morning_beginning: DateTime<Utc> = Utc
        .with_ymd_and_hms(
            utc.year(),
            utc.month(),
            utc.day(),
            MORNING_BEGINNING_HOUR + US_EASTERN_OFFSET,
            0,
            0,
        )
        .unwrap();

    let just_before_morning = morning_beginning - chrono::Duration::nanoseconds(1);

    let just_after_morning = morning_beginning + chrono::Duration::nanoseconds(1);

    utc > just_before_morning && utc < just_after_morning
}

#[cfg(test)]
mod tests {

    use super::is_morning;
    use crate::{TimeSource, MORNING_BEGINNING_HOUR, US_EASTERN_OFFSET};
    use chrono::{DateTime, Datelike, TimeZone, Utc};
    use std::sync::{Arc, Mutex};

    #[test]
    pub fn beginning_of_morning() {
        struct FakeTimeSource(Arc<Mutex<DateTime<Utc>>>);

        impl TimeSource for FakeTimeSource {
            fn now() -> DateTime<Utc> {
                let utc = Utc::now();

                let beginning_of_morning = Utc
                    .with_ymd_and_hms(
                        utc.year(),
                        utc.month(),
                        utc.day(),
                        MORNING_BEGINNING_HOUR + US_EASTERN_OFFSET,
                        0,
                        0,
                    )
                    .unwrap();

                beginning_of_morning
            }
        }

        assert_eq!(is_morning(), true);
    }

    #[test]
    pub fn just_before_morning() {
        assert_eq!(is_morning(), false);
    }
}

FakeTimeSource is created but never used.

To expound further, is_morning always uses a DateTimeSource. In order to do what you want, you would have to pass in a T: TimeSource into is_morning, and then call now() on that.

You could have two methods, one that takes in the TimeSource, and one that creates a DateTimeSource and passes it to the first one, if you want to still have the ease of calling in_morning() as is, but still test the underlying logic

pub fn is_morning() -> bool {
    let utc = DateTimeSource::now();
   //         ^^^^^^^^^^^^^^^^^^^^^ Always use the Utc::now() implementation

Also, EST is UTC-5 not UTC+5. (Also, EDT exists.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.