Our team decided to open source this as we think it could benefit the whole rust community. Also we are seeking feedback from the community to make it better: injectorpp
In short, injectorpp
allows you to mock functions without using trait.
For example, to write tests for below code:
fn try_repair() -> Result<(), String> {
if let Err(e) = fs::create_dir_all("/tmp/target_files") {
// Failure business logic here
return Err(format!("Could not create directory: {}", e));
}
// Success business logic here
Ok(())
}
You don't need trait. Below code just works
let mut injector = InjectorPP::new();
injector
.when_called(injectorpp::func!(fs::create_dir_all::<&str>))
.will_execute(injectorpp::fake!(
func_type: fn(path: &str) -> std::io::Result<()>,
when: path == "/tmp/target_files",
returns: Ok(()),
times: 1
));
assert!(try_repair().is_ok());
Why
In my current career as a software developer, I saw most if not all the articles are asking developers to use interfaces to make the code unit testable. After seeing many "interfaces" or "traits" that are used solely for testing purpose and made the project complex, I start to doubt it. Is the test only interface really worth it? Should the interface be used for business need instead of only for making code unit testable? Can we have a way to test our code without introducing these tests only interfaces?
That's the reason why injectorpp is here.
Share your thoughts. Happy to discuss 
1 Like
It's interesting to see monkeypatching in Rust. Will try to give it a try in the next couple of weeks!
I'm curious, is there any writeup on what mechanism this uses? It seems to me like the only way to do this would be to patch code in memory, which would depend on debug info, be unreliable if inlining happens and is almost certainly undefined behaviour.
Or did you figure out something else that I missed?
It's a bit beyond me, would also be interested in a write up.
It seems to involve injecting jumps via assembly, at least on x86_64
Pretty gnarly low level stuff, they also document recommend profile.test settings in the readme to help it work so I think you basically have the right idea. Hopefully not instant undefined behaviour, I am not gonna suggest it does or doesn't as I simply don't know 
2 Likes
Hi @Vorpal ,
In high level concept it's like implementing a JIT compiler. The same function could have different machine code in different platforms at runtime. Here the "platforms" are production and test. In production, the machine code is not changed. In test, the machine code is different.
I would say it does not depend on debug info except the inline. Afterall the memory address of a function needs to be there at the beginning. That's why there's a recommended [profile.test]
setting to prevent optimization.
Besides that, as long as the function memory address can be captured, the rest implementation is pretty much well defined behavior. Yeah it's a JIT compiler to translate the function to different machine code.
1 Like
Hi @drmason13 ,
Yeah you get the point. The only prerequisite right now is the memory address of a function. That's why [profile.test]
settings are recommended. Besides that, the low-level implementations are conceptually similar like implementing a JIT compiler. Translate the function to different machine code according to your platforms. As long as we know what the machine code is doing, it's not undefined behavior. Similar concept to how JIT compilers are implemented 
1 Like
Hi @firebits.io ,
Thank you! Do let me know if you find any issue or have any suggestions.
Potentially relevant / cleaner way of doing this (but does require nightly currently, but the Linux kernel also wants this, so there is hope): 3543-patchable-function-entry - The Rust RFC Book
2 Likes
Looks very interesting! It does make things easier if it can be included in rust.
I forgot to mention, one of the big challenges that solved by injectorpp in our team is faking system functions. Projects rely on low-level system APIs always have such challenges.
Below is an example for faking shm_open
#[test]
fn test_fake_shm_open_should_return_fixed_fd() {
// Fake shm_open to always return file descriptor 32
let mut injector = InjectorPP::new();
injector
.when_called(injectorpp::func!(shm_open))
.will_execute(injectorpp::fake!(
func_type: fn(_name: *const c_char, _oflag: c_int, _mode: c_uint) -> c_int,
returns: 32
));
let name = CString::new("/myshm").unwrap();
let fd = unsafe { shm_open(name.as_ptr(), 0, 0o600) };
assert_eq!(fd, 32);
}
Hello,
Thanks for sharing!
Did you experience using it to mock other of the typical external dependencies in a project, i.e database crates? Just like the way you mock fs functions, i'm evaluating possibilities to mock other dependencies.
Thanks!
Hi,
Could you share a sample database crate and code that you'd like to mock?
Hello,
I'm thinking of ORMs more precisely, i.e diesel in rust (or sea orm but this one has a mock interface provided) to mock the queries:
let versions = Version::belonging_to(krate)
.select(id)
.order(num.desc())
.limit(5);
Regards