"Mocking" in the sense of examining which parameters have been passed to a function
Using crate mockall, here's a possibility for mocking a function from an outside module which is used by the internal methods/functions of your app code.
My outside module (in fact a self-contained crate) is called "utilities". It contains a function I want to mock to examine and run checks on the parameters passed to it, called reqwest_call
(simplified in what follows). Also see crate reqwest: the main Rust workhorse equivalent to Python requests
package.
Cargo.toml of the crate which uses crate utilities
has to have the following dependencies:
[dependencies]
...
mockall_double = "0.3.1"
[dev-dependencies]
...
mockall = "0.12.1"
(the main mockall
crate does NOT have to be included in releases. But the smaller one, mockall_double
, does).
What follows is all happening at the bottom of the file containing the struct-impl
you want to test (i.e. it doesn't work in the separate "tests" directory of the module).
use mockall_double::double;
// alias the imported function
use utilities::reqwest_call as real_reqwest_call;
// let mockall create a "double"
#[allow(unused_imports)]
#[double]
use test_utilities::reqwest_call;
// set up mocking
mod test_utilities {
use super::*;
#[allow(dead_code)]
#[cfg(not(test))]
pub fn reqwest_call(url: &str, method: reqwest::Method)
-> Result<ResponseObject> {
// here is where the REAL method gets called in the case of a run (not testing)
real_reqwest_call(url, method)
}
#[allow(dead_code)]
#[cfg(test)]
pub fn mock_reqwest_call(url: &str, method: reqwest::Method)
-> Result<ResponseObject> {
// this is where your parameter-testing code goes
assert!(url.contains(...));
...
Ok(ResponseObject::new()) // for example
}
}
// the mod containing the tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_something() -> Result() {
...
let _ = tds.do_bulk_insert();
...
In the actual app code the method do_bulk_insert()
calls reqwest_call
at some point... maybe even several times. During an app run the real function gets called (via real_reqwest_call
).
But during a test run the configured function mock_reqwest_call
, in mod test_utilities
, gets called instead each time. Note that mockall creates the "mock" function mock_reqwest_call
(with the characters "mock_" prepended) automatically.
If the parameters passed fail the assert!
s in the function mock_reqwest_call
, the test fails.
Yes, quite a rigmarole compared to pytest
. And as I say, this won't work in files under directory "tests", because cfg(test)
is always false
in these files (in fact use of a feature might provide a way to get around that, but that's beyond the scope of this answer).
Another limitation is that this checking of the parameters will run in the same way in all tests. What you need is the ability to branch conditionally, inside the mocked function, depending on the name of the currently running test. There has been some discussion on this but it's not yet implemented as far as I'm aware (someone please suggest an edit if you know better).
As it happens, an acceptable workaround in my example is not too difficult to find, however: for example, the param url
can be engineered to reveal which test is being run when the mock function is called. However each case will be different: human ingenuity may be required to contrive to make sure your mocked function can be passed a parameter (during testing) which somehow reveals which test is being run.
(NB again, the use of a feature might be a way of switching on and off mocking for given test functions ... but again, kind of beyond the scope of this answer).
foo()
is unconditionally callingget_user_input()
, and there is nothing you can do purely in the test to change that. You need to write your code in a way to allow injecting a different function. The easiest way is to define a trait, sayUserInput
, with an assoicated functionget()
, and makefoo()
generic overT: UserInput
. You can then have a standard implementation forUserInput
for the actual code, and another one just for the tests. – Gley