Proper way to mock in rust

In case that I have a structure that has two static methods

struct Foo {}

impl Foo {
    fn get_const(x: u16) -> u16 {
        x
    }

    pub fn foo_fnc() -> u16 {
        let t: u16 = 1;
        // Some work
        let r = Foo::get_const(t);
        // Some work
        t
    }
}

}

and I want to mock get_const so I can test the public static method, how it is possible with MockAll ?

I've tried this :

#[automock]
impl Foo {

    fn get_const(x: u16) -> u16 {

        x

    }

    pub fn foo_fnc() -> u16 {

        let t: u16 = 1;

        // Some work 

        let r = Foo::get_const(t);

        // Some work

        t

    }

}

#[tokio::test]
async fn test_get() {
    let ctx = MockFoo::get_const_context();
    ctx.expect().returning(|_| 16);
    assert_eq!(MockFoo::foo_fnc(), 16);
}

but it doesn't work.

panicked at 'MockFoo::foo_fnc(): No matching expectation found'

The same question for non-static methods.

Thanks.

Try creating a trait that defines those two methods, and mock the trait instead.

1 Like

You can also use cfg's for this, so that when you compile for testing your methods/function/types etc are different.

The best approach to this really depends on what you're actually testing

1 Like

Can you please provide an example ? as I'm a new to the community I don't have much knowledge about the traits and cfgs.

I'm trying to use the examples the mockall doc but I couldn't make it working in my use case.

I've never used mocking frameworks because they tend to be too magical and are normally used as a crutch because your components don't have well defined layers/interfaces.

Instead, the idea is to use Dependency Injection to introduce an interface for the things that your code depends on so you can provide an alternate implementation during testing.

For example, say I was writing some code which uses the clock to time how long a function takes to execute.

First I'd introduce an interface with the behaviour I want to mock out.

use std::time::Instant;

trait Clock {
    fn current_time(&self) -> Instant;
}

Then I'll create the implementation that we'll be using in the real application.

/// A [`Clock`] which uses the operating system to determine the time.
struct SystemClock;

impl Clock for SystemClock {
    fn current_time(&self) -> Instant {
        Instant::now()
    }
}

Next I'll write the function that is meant to be tested. This takes something implementing the Clock trait and the function I want to time.

/// Execute a function and time how long it takes.
fn time_it<C, F>(clock: &C, func: F) -> String
where
    C: Clock,
    F: FnOnce(),
{
    let start = clock.current_time();
    func();
    let end = clock.current_time();

    format!("Executed in {:?}", end - start)
}

Finally I'll create a mock implementation which lets me change the "current time" (now) whenever I want.

use std::cell::RefCell;

struct MockClock {
    now: RefCell<Instant>,
}

impl Clock for MockClock {
    fn current_time(&self) -> Instant {
        *self.now.borrow()
    }
}

And here's a function which uses our system clock and mocked clock.

fn main() {
    let system_clock = SystemClock;
    let msg = time_it(&system_clock, || {
        // Do nothing.
    });
    println!("Using the system clock: {:?}", msg);
    
    let mock = MockClock { now: RefCell::new(Instant::now()) };
    let msg = time_it(&mock, || {
       // tell the mock to increment time by 1 second
       *mock.now.borrow_mut() += Duration::from_secs(1);
       // Do nothing
    });
    println!("Using the mock clock: {:?}", msg);
}

Compiling and running this on my machine shows it completes in about 3ms.

$ time ./main
Using the system clock: "Executed in 243ns"
Using the mock clock: "Executed in 1s"
./main  0.00s user 0.00s system 84% cpu 0.003 total

(playground)

4 Likes

@Michael-F-Bryan Thanks for the explantation, my last question is how I can implemente both MockClock and SystemClock in my code and then use MockClock in tests only and SystemClock in prod ? In case that I want to test a function that use a SystemClock

You are writing the code so you have control over when the clock is created and which one to use. It's just a case of only using MockClock in your tests and SystemClock in production.

You can also use the SystemClock like normal in your tests if it doesn't need to be mocked out.

1 Like

You could do something like this:

struct Foo{}

impl Foo{
    #[cfg(not(feature = "mockdata"))]
    async fn get_data() -> Vec<u8>{
        // fetch a file over the network
    }
    
    #[cfg(feature = "mockdata")]
    async fn get_data() -> Vec<u8>{
        // get a file from your local file system
    }
}

That way you compile different code depending on whether you use the "mockdata" feature flag or not.

Something like this could be useful if, for example, you want to run tests in an environment without network access (like a docker container).

2 Likes

One approach to mocking is to make the function you are trying to mock, "mock aware". Consider this code for example:

pub fn page<T, K, V>(
    url: &str,
    query: std::collections::HashMap<K, V>,
    tid: Option<String>,
) -> crate::Result<T>
where
    T: serde::de::DeserializeOwned,
    K: Into<String> + AsRef<str> + Ord,
    V: Into<String> + AsRef<str>,
{
    let url = to_url_with_query(url, query)?;

    if crate::is_test() {
        return crate::mock(tid, serde_json::json! ({"url": url.as_str()}));
    }

    crate::handle(crate::client(url.as_str(), reqwest::Method::GET))
}

Here I am trying to make an API request. Instead of always calling backend, I check if we are in test mode, if so we return mocked response.

This is part of ft-cli repo if you want to checkout the full code.

1 Like

Your problem is that foo_fnc itself is a mock function. Mockall mocks every function in the struct, not just some of them. So in your test, you need to set an expectation on foo_fnc. On the other hand, if you're trying to test the implementation of foo_fnc, then you must move get_const into a separate struct, and mock that, not Foo. Like this:

struct Inner {}
#[automock]
impl Inner {
    fn get_const(x: u16) -> u16 { ... }
}
struct Foo { ... }
impl Foo {
    pub fn foo_fnc() -> u16 {
        let t: u16 = 1;
        // Some work 
        let r = Foo::get_const(t);
        // Some work
        t
    }
}
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.