peeriot / murf

Mocking and Unit test Framework for Rust
MIT License
31 stars 0 forks source link

How to use murf without chaning my code? #8

Open szabgab opened 1 month ago

szabgab commented 1 month ago

Hi,

as I understand it in all the examples in the README, the code in main() was using some murf-code. e.g. let (handle, mock) = MyStruct::mock_with_handle();. Is it possible to use murf without changing my application code. Only in the test code? e.g. here is a "real world" example where one function takes a long time to run. I'd like replace it during testing so the test would run faster:

pub fn add(left: u64, right: u64) -> u64 {
    long();
    left + right
}

pub fn long() {
    std::thread::sleep(std::time::Duration::from_secs(20));
}

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

    #[test]
    fn it_works() {
        // replace the long() function to return immediately
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

And another version of the code, this time using a struct:

#![allow(dead_code)]

struct Rectangle {
    length: u32,
    width: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.long();
        self.width * self.length
    }
}

impl Rectangle {
    fn long(&self) {
        std::thread::sleep(std::time::Duration::from_secs(20)); 
    }
}

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

    #[test]
    fn it_works() {
        let r = Rectangle { length: 2, width: 3};
        assert_eq!(r.area(), 6);
    }
}
Bergmann89 commented 1 month ago

Hey szabgab,

you need a suitable interface to get the mocked code into your code under test, so you always have to design your application in such a way. Without any change in the application it is nearly impossible to get the mocked code in (there are some possibilities, but they are not supported by murf).

In your special case I see two different possible solutions:

1) Move the code that takes to long to a trait and then make your code generic:

trait Long {
    fn long();
}

struct Rectangle<T: Long> {
    length: u32,
    width: u32,
}

impl<T: Long> Rectangle<T> {
    fn long(&self) {
        T::long();
    }
}

Then you can use the real implementation of the Long trait during run time and the mocked version during testing. Of cause this solution might cause a lot of changes in your code because you have to make your types generic.

2) Use a compiler switch to use a mocked version during test time:

struct Rectangle {
    length: u32,
    width: u32,
}

impl Rectangle {
    fn long(&self) {
        long::Long::long();
    }
}

#[cfg(not(test))]
mod long {
    struct Long;

    impl Long {
        pub fn long() {
            println!("Real long").
        }
    }
}

#[cfg(not(test))]
mod long {
    mock! {
        struct MockedLong;

        impl MockedLong {
            fn long();
        }
    }

    pub type Long = mock_impl_mocked_long::Mock<'static>;
}

// somewhere in the test code
let mocked_long = MockedLong::mock();

expect_call!(mocked_long, long()).will_once(Invoke(|| println!("Mocked long")));

This solution is less intrusive but a little bit more complex and unfortunately not yet fully supported by murf. But we could add this relatively simple.

szabgab commented 1 month ago

Thanks

Bergmann89 commented 1 month ago

Does the first solution work for you, or should we schedule the implementation for the second one?

szabgab commented 1 month ago

I did not have the time to understand it yet. I am just experimenting and trying to figure out how could I mock things. So thanks for the offer, but don't do anything just for me.

Bergmann89 commented 1 month ago

Ok, take your time. If you need more support fell free to ask again :)