How do I create a mock function in Rust?

I'm trying to run unit tests on a function reducer . reducer takes in a struct State and an enum Action and returns a new struct State . When the action is Action::Status , I want the function state.status_function to be called on state. I'm having trouble testing that a mock function I pass into test_status is called with state. Does anyone know what I'm getting wrong in the code below?

fn test_status() {
    let mut status_mock_called: bool;
    let state = State {
        status_function: Box::new(|state: State| {
             status_mock_called = true;
             return state;
        }),
     };
     assert_eq!(root_reducer(state, Action::Status), state);
     assert!(status_mock_called);
}

Outputs the error:

`(dyn std::ops::Fn(State) -> State + 'static)` doesn't implement `std::fmt::Debug`

How can I modify a rust variable from inside a function?
Here is the state struct in case it's relevant:

#[derive(Debug, Eq, PartialEq)]
struct State {
    status_function: Box<dyn Fn(State) -> State>,
}

And here is the reducer:

fn root_reducer(state: State, action: Action) -> State {
    match action {
        Action::Status => (state.status_function)(state),
    }
}

You can generally use a closure, in cases like this. Your State struct definition will have to change to store a boxed closure. You will probably need State to implement Clone if you want to both store it in a variable and return the value from the closure. Rc or another shared smart pointer type could help with that.

1 Like

For example: Rust Playground

2 Likes

Are you talking about the box from the standard library? I'm really new to Rust.

Yes. Have a look at the link AndrewGaspar provided, that shows the general idea.

1 Like

I'm getting

help: the trait `std::fmt::Debug` is not implemented for `dyn std::ops::FnMut(bool)`

, among other errors, and I need certain derive calls to compare structs using assert.

You're probably not calling the closure. Try adding (true) to the end of the closure, which would call it and using true as the argument. You can see that in the link AndrewGaspar provided

1 Like

I've tried calling it inside reducer, but it still is giving me that error.

Can you share your current code that gives the error?

1 Like

Sure.

Updated!

Your error is that there's no default implementation for Debug for function types. For the State type I would remove the Debug in #[derive(...). If you really want debug for some reason, I think you'd have to implement it yourself.

And FYI in the future, rather than modifying the code in place in the first post, its better if you just put your new code in a reply. That way if someone comes across this with similar issues, they can follow along and see where you started and where you ended up.

1 Like

With no derive debug, I'm getting

the trait bound `(dyn for<'r> std::ops::FnMut(State<'r>) -> State<'r> + 'a): std::cmp::Eq` is not satisfied`

when I try to run assertions.

Sorry I missed that. I don't believe there are Eq or PartialEq derives for functions either. I would just remove the #[derive(...)] altogether. If you need equality on your state struct (and want it to hold function pointers like it is), you'll need to manually implement those traits as well and compare any variables within state that can be. That's assuming you're not interested in comparing the equality of the function pointers.

1 Like

When I make assertions between States it's still giving me errors. Do I need to manually implement the traits? Testing in Rust is sounding a little complicated..

Are you trying to compare equality of two states? It looks like you're making some sort of reactive programming implementation. I've only ever played with that with react and redux. But I believe that the state would typically be a POD structure Link. If you make your state only data, the derive Eq and PartialEq should work just fine. Unless the status function shown changes during the definition of the program, you can change that to be just a function definition in an impl State {} block that is defined for the life of the program.

If you really do need to compare the equality of function pointers to implement what you're trying to do, I've never really tried to do that in Rust, so someone else would need to chime in to help there.

1 Like

Oh cool. I didn't know that POD was possible in Rust. How can I have just a plain key/value store that's not a struct? Is that what trait is for?

Depends on what you want to store in the data structure. If you really have a no idea what you want to put in it or the values are coming from input outside of rust, you could look at making your state object as something like an instance of a serve_json::map::Map. This type uses strings as keys and then serve_json::value::Value as a the value. The Value type is defined as follows. Since rust has stricter types compared to javascript, its definitely more work than javascript to do something like this, but it is certainly possible. The Map type already implements Eq so I think it should work where your current State struct is failing.

pub enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}
1 Like

I don't know about that. I feel like some of the types in my data structure are pretty diverse, from closures to u64s. Wait a sec, I could just put my functions in the actions and everything would be solved

So I'm trying to transfer the Status action to a tuple enum where I can call status_function from the action.

type StatusFunctionType = Box<dyn FnMut(State) -> State>;

#[derive(Debug, Eq, PartialEq)]
struct State {
    date: Date<Utc>,
    miles: u64,
    food: u64,
    health: u64,
    hunt_days: i64,
    rest_days: i64,
    travel_days: i64,
    travel_distance: u64,
}

enum Action {
    Hunt,
    Travel,
    Rest,
    Status(StatusFunctionType),
}

The problem comes after I try to pass in a mock function into it.

fn test_status() {
        let state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 1970,
            food: 500,
            health: 5,
            hunt_days: 2,
            rest_days: 2,
            travel_days: 3,
            travel_distance: 30,
        };
        let mut status_mock_called: bool;

        let status_mock = Box::new(|state: State| -> State {
            status_mock_called = true;
            return state;
        });
        assert_eq!(root_reducer(state, Action::Status(status_mock)), state);

        assert!(status_mock_called);
    }

There are 3 errors.

status_mock_called` does not live long enough
borrow of moved value: `state`

value borrowed here after move
cannot use `status_mock_called` because it was mutably borrowed

I'm watching a video on ownership and borrowing because I don't understand next to anything about that :slight_smile:.