How to monkeypatch (mock) calls to ncurses crate?

Viewed 138

I come from Python where you have monkeypatch in pytest, so I refer to the operation I want to do as monkeypatching, however, I've been told that the actual operation is called mocking more generally.

Anyway, my application uses the ncurses crate, and I need to test some code that calls it. How would I monkeypatch (mock) the calls to crate's functions, methods, and other attributes properly?

For example,

fn get_page(&self, commands: &[String]) -> Vec<String> {
    match commands
        .chunks(ncurses::LINES() as usize - 3)
        .nth(self.page as usize - 1)
    {
        Some(cmds) => cmds.to_vec(),
        None => Vec::new(),
    }
}

How would I patch the ncurses::LINES() call?

1 Answers

In python everything is dynamic and patchable, but rust is compiled and concrete. So in your source code you have to explicitly decouple your working code and the library code.

trait NcursesImpl {
    // all functions in ncurses that you want to use need to have implementations here.

    /// Here is a default implementation that just delegates to ncurses
    fn LINES(&self) -> c_int {
        ncurses::LINES()
    }
}
struct DefaultNCruses;
impl NCursesImpl for DefaultNCurses {
    // no functions overridden, everything uses default implementations
}
struct LinesLiarNCurses {
    lines: c_int,
}
impl NCursesImpl for LinesLiarNCurses {
    /// overrides default implementation for debug purposes
    fn LINES(&self) -> c_int {
        self.lines
    }
}

get_page() has to change slightly to use a NCursesImpl instead of the bare ncurses.

fn get_page(&self, nc: &NCursesImpl, commands: &[String]) -> Vec<String> {
    match commands
        .chunks(nc.LINES() as usize - 3)
        .nth(self.page as usize - 1)
    {
        Some(cmds) => cmds.to_vec(),
        None => Vec::new(),
    }
}

I'm not exactly sure where the NCursesImpl object should live. Here I've made it an explicit parameter of the get_page call, but it might actually belong as a member of Self in the struct that has the get_page impl. You might want to store it in a Box<dyn NCursesImpl> so that you don't have type parameters littering your data structures.

Related