/// Individual IP/name tuple
#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
pub struct Ip {
/// IP, can be IPv4 or IPv6
pub ip: IpAddr,
/// hostname.
pub name: String,
}
I also have a solve()method to convert IP into name using the lookup_addr()function from the dns-lookup crate.
impl Ip {
// ...
pub fn solve(&self) -> Self {
let ip = self.ip;
let name = match lookup_addr(&ip) {
Ok(nm) => {
// No PTR, force one
if ip.to_string() == nm {
"some.host.invalid".into()
} else {
nm
}
}
Err(e) => e.to_string(),
};
Ip { ip, name }
}
}
I'm trying to write a test for Ip::solve() and want to avoid network latencies and timeouts by mocking lookup_addr(). I've found the double and mockall crates which have similar methods for mocking, generally with defining a trait with the function to mock and using a combination of macros to implement the mocking. I can't get it to work at all.
So, how would you do it? The go approach I used before (see here was to have an interface (equiv. to a trait more or less) but Rust is way more complicated. I'm a bit lost.
I would just do DI. Make a trait DnsResolver { fn lookup_addr() -> Result<...> } then have a struct RealDnsResolver that impl DnsResolver for RealDnsResolver, and also a impl DnsResolver for FakeDnsResolver.
Pretty much like what they do in Java and any other OOP language. Interface abstracting side-effects, then injecting it where needed, instead of hardcoding behavior.
I remember Rust traits and dependency injection - Julio Merino (jmmv.dev) was a decent article with some good ideas around the problem. Frankly, I think Rust is lacking here and could use a good and popular library to help with the boilerplate w.r.t defining traits and then using Box/Arc< dyn Trait> objects that implement them, but most people that look at the problem jump straight to building some pointless Springboot-like DI containers and alikes which no one in Rust community will want to use (because they are a bad idea).
Thanks for the reference of the article, I'll read it. In the end, that's how I'm going to go, it is the same approache I have in Go but implicit interfaces and func pointers makes it slightly easier there. Things like Box<dyn Resolver> are not Copy so it makes other stuff harder to have the pointer around.
and have a shared switch bool flag switch the behavior of SwitchableDnsResolver from anywhere in your program at runtime. You can literally implement anything.
You need it shared? Arc<dyn Resolver + Send + Sync> it is. .clone() it like there's no tomorrow, send between threads, you name it.