rust-pcap / pcap

Rust language pcap library
Apache License 2.0
595 stars 138 forks source link

Recommendations on how to mock Rust pcap? #284

Closed robs-zeynet closed 8 months ago

robs-zeynet commented 1 year ago

Hi - thanks for the great library!

To improve testing in my project, I'm trying to create a mock of the pcap library; specifically the Capture struct ... and it's not obvious how to do it. I'm looking at using 'mockall' because it allows you to mock structs and not just traits, but I still think I'll end up creating a wrapper trait that calls out the functions I need and then I can still use the mockall::automock feature. Has anyone else tackled this and come up with a better solution? Or alternatively, would the rust pcap community consider a patch that refactors Capture so that all functions are available through a trait in addition to the struct implementation to ease mocking, e.g.,


#[mockall::automock]
trait Capture<T: State+ ?Sized> {
    // ... all of the function definitions exposed by Capture for each of the various states
}

struct CaptureImpl {
  // ... actual data elements
}

impl CaptureImpl for Capture {
  // ... actual code that implements the Capture trait
}

Thoughts? I can't be the first person who thought about mocking this, so I'm hoping I'm just thinking about this wrong. If not, happy to put the patch together. Feedback welcome.

Thanks in advance.

Wojtek242 commented 1 year ago

I think that adding traits in addition to the struct, thus creating a double implementation of everything will create an unnecessarily large maintenance burden. A refactor that increases mockability without additional overhead is welcome though. However, other than that I don't know how to mock rust pcap as I don't have much experience with mocking in rust myself.

Wojtek242 commented 1 year ago

I have recently been playing with Rust in a personal project of mine and refactoring such that the public interface is defined through traits, but implemented in structs is, now that I think of it, a much better approach. However, I personally don't have time to implement that. But if you (or somebody else) would like to, that is welcome.

It would require a 2.0.0 release though, but that's fine as it sounds like the benefit would be significant enough.

Stargateur commented 1 year ago

trait in Rust are used "shared behavior" what behavior do you want a trait in pcap do ? The only thing that come to my mind is iterating the packet and for that I already put some work. What other behavior could we put on a trait that make sense ?

Also, the system of state that avoid at compile time mistake would be impossible to replicate.

Wojtek242 commented 1 year ago

What other behavior could we put on a trait that make sense ?

Anything that's a public interface. Then you can implement the interface with an actual struct when compiling the binary for real use or with a mock struct when compiling for testing. I use this in my other personal project and it really helps with mocking.

It's also generally a good principle for both the user code and the implementation code to both rely on an abstract interface definition rather on concrete instances of each other.

Stargateur commented 1 year ago

I kind of disagree, that not the recommend way to code in rust, you can simply see the big decision of rust to not have a "collection" trait in std. Not a "Command" trait, etc...

Wojtek242 commented 1 year ago

If it's not the recommended way to do it Rust (I don't know, I only dabble in Rust in my spare time), do you know what the idiomatic way of implementing https://en.wikipedia.org/wiki/Dependency_inversion_principle in Rust is?

Stargateur commented 1 year ago

well there is not, rust is not OOP, it's data oriented, aka structure. I have see many try to do mock in rust and so far I have see nothing good.

Wojtek242 commented 1 year ago

Rust being OOP or not should not make it impossible to implement https://en.wikipedia.org/wiki/Dependency_inversion_principle. At its core, it just states to depend on abstractions (which in OOP is an interface or something like that) and that dependencies should be structured in a certain way - UML and OOP is just one way of implementing it.

In Rust this can be achieved with traits. Define an abstract interface using a trait, make the concrete implementation depend on the trait and make users depend on the trait. That way, you can simply replace both user and implementation code freely. pcap could have a trait defining its public interface and perhaps one day somebody can then write pcap-alt and the trait would be the shared behaviour between the implementation.