Let's say I have this:
fn something(path: String) {
let contents: Vec<u8> = std::fs::read(path);
...
}
How to mock the call to std::fs::read()
? I know Rust doesn't support mocking out of the box, but even using libs like mockall, I can't see how this could be done.
These libs can mock traits and structs defined by me, because I can annotate those, but I don't see how this is supposed to work with the standard library modules.
Maybe mockall is not the right tool? What lib should I use?
You can conditionally select which read
to use a few different ways.
In this example we define our own read
only if tests are being built, otherwise we import std::fs::read
. Then we can just use read
and know we'll get one or the other depending on whether tests are being built.
use std::path::Path;
#[cfg(not(test))]
use std::fs::read;
#[cfg(test)]
fn read(p: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
todo!()
}
fn example() {
let data = read("path").unwrap();
}
fn main() {
example();
}
#[cfg(test)]
mod tests {
#[test]
fn example() {
super::example();
}
}
In this case we use the cfg!
macro to decide which function to call at the call site. The optimizer will eliminate the other branch as dead code.
The disadvantage to this approach is having to duplicate the condition at every location you need to invoke read
. The advantage is it's very clear to the reader that mocking happens during tests without having to inspect the rest of the file to see what read
is.
use std::path::Path;
fn mock_read(p: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
todo!()
}
fn example() {
let data = if cfg!(test) {
mock_read("path")
} else {
std::fs::read("path")
}
.unwrap();
}
fn main() {
example();
}
#[cfg(test)]
mod tests {
#[test]
fn example() {
super::example();
}
}