pub fn find_available_ports() -> Result<Vec<SerialPort>, SerialError> {
// Return vec of all ports found on device
match serialport.available_ports() {...
}
}
Where I'd like to test my code from inside this function (even though it doesn't do too much). The "rust" way of implementing this is escaping me though. Ideally I'd like to use mockall to fake the results from serialport::available_ports and pass in some kind of context to the function to either use the external serialport function call or my mock one.
I was thinking I can pass in an option with a SerialManager struct that impl a trait with the external or mock function but can't seem to wrap my head around the correct way to do this:
pub fn find_available_ports(
conn: Option<SerialManager>,
) -> Result<Vec<SerialPort>, SerialError> {
let connection = conn.unwrap_or(SerialManager {});
// Return vec of all ports found on device
match connection.available_ports() {
You're just passing a struct in that code, so either the struct would have to wrap something like a trait object or a function pointer, or you'd need to use generics and a trait.
Here's a loose outline of how you might go about using a trait and generics. Playground
#![allow(dead_code)]
// Stub types to make the given signature compile.
struct SerialPort;
struct SerialError;
/// A trait that abstracts over the function(s) you want to mock out in tests
trait SerialManager {
fn available_ports(&self) -> Result<Vec<SerialPort>, SerialError>;
}
/// A struct which implements the trait to call the real function.
struct RealSerialManager;
impl SerialManager for RealSerialManager {
fn available_ports(&self) -> Result<Vec<SerialPort>, SerialError> {
// Call the actual implementation
todo!()
}
}
/// A struct which implements the trait to return mock data without calling the "actual" implementation.
struct MockSerialManager;
impl SerialManager for MockSerialManager {
fn available_ports(&self) -> Result<Vec<SerialPort>, SerialError> {
// Return mock data.
todo!()
}
}
/// The function that is generic over the manager. Can be private if desired.
fn find_available_manager_ports<M: SerialManager>(
manager: M,
) -> Result<Vec<SerialPort>, SerialError> {
// Return vec of all ports found on device
match manager.available_ports() {
Ok(_) => todo!(),
Err(_) => todo!(),
}
}
/// A simpler function that calls the inner one with the "real" manager implementation. This allows you to call the function just as before in your normal code.
///
/// Any code that needs to call the mock version indirectly would also need to be generic over the manager type.
fn find_available_ports() -> Result<Vec<SerialPort>, SerialError> {
find_available_manager_ports(RealSerialManager)
}
Another option is to use if cfg(test) in the implementation to mock the behavior, and/or #[cfg(test)]and #[cfg(not(test))] to completely replace structs, functions and entire modules.