How to properly unit test with mock injection

Hi, I currently struggle with a simple pattern to properly use mock in a unit test. In most language you would normally make a constructor where you can inject your mock and have an overload where you do not take any parameter so the normal object creation you occur.

I manage to get something like that to work

pub trait Internal {
    fn connect<T: AsRef<Path>>(&self, path: T) -> io::Result<()>;
}

impl Internal for UnixDatagram {
   fn connect<T :AsRef<Path>>(&self, path: T) -> io::Result<()> {
       self.connect(path)
    }
}

pub struct Client<T: Internal> {
   socket: T
}

impl Client<UnixDatagram> {
    pub fn new() -> Self {   ....    }
}

impl<T: Internal> Client<T> {
    pub fn new_internal(socket :T) -> Self {
        Client{socket}
    }
}

With this approach I can freely change the socket implementation in my test, but I need to 'leak' the Internal trait from outside my module. Any tips or advice on this ?

I suggest you only use mocks in your unit tests, not your integration tests. That way you won't have to leak the details out of your crate. Furthermore, I suggest you use Mockall to help you write your tests. It can mock structs, so you won't need to define the Internal trait. You can do it a little like this:

use mockall_double::double;

mod internal {
    use mockall::automock;
    pub struct UnixDatagram {
        ...
    }
    #[cfg_attr(test, automock)]
    impl UnixDatagram {
        ...
    }
}
#[double]
use internal::UnixDatagram;

pub struct Client {
    socket: UnixDatagram
}
impl Client {
    #[cfg(test)]
    pub fn new_internal(socket: UnixDatagram) -> Self {
        ...
    }
}
...
#[cfg(test)]
fn test_client() {
    let mut mock_socket = UnixDatagram::default();
    mock_socket.expect_connect()
        .with(...)
        .return_const(...);
    let client = Client::new_internal(mock_socket);
    ...
}

Sorry for the late reply. In your example, are you missing a dyn keyword like so

pub struct Client {
    socket: dyn UnixDatagram
}

Other wise the size won't be know at compile time. You can always use a box right ? For my case the trait have more method with a generic type. So I can't have a trait object.

Thanks

UnixDatagram is a struct, not a trait, mockall::automock can also mock structs' impl.

Yes this work if the struct is in a module I owned. But in this case UnixDatagram is from std::os::unix::net module. So I need to use the mock! macro to create the mock. I think this is what the first answer was proposing. So yeah no dyn require and no generic. Sorry I was not aware of the double macro.

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.