Help Mocking A Function / Module

I am trying to write a unit test for my main function where I mock the "process_csv_file" function and return some hardcoded value. Here is my code right now:

main.rs

mod helpers {
    // #[mockall::automock()]
    pub(super) mod csv_processor;
}

use std::error::Error;

use crate::helpers::csv_processor::process_csv_file;

fn main() -> Result<(), Box<dyn Error>> {

    let hardcoded_file_path = "./csv/example_file.csv".to_string();

    match process_csv_file(hardcoded_file_path) {
        Ok(result) => println!("CSV file processed successfully: {}", result),
        Err(e) => eprintln!("Error: {}", e),
    }

    Ok(())

}

#[cfg(test)]
mod main_unit_tests {

    // mock "process_csv_file" function call, return value

    // expect string with mocked value to be printed

}

/helpers/csv.processor.rs

use std::error::Error;

pub fn process_csv_file(_file_path: String) -> Result<String, Box<dyn Error>> {

    Ok("real".to_string())
}

When I uncomment the "automock" line I get this error:

custom attribute panicked
message: automock can only mock inline modules, not modules from another file. More information may be available when mockall is built with the "nightly" feature.

What is the proper syntax for mocking this module? thanks

The automock attribute is applied to a Trait definition or an impl block, not to a mod import.

For example, the following would work:

In src/helpers/csv_processor.rs:

use std::error::Error;

#[cfg(test)]
use mockall::{automock, predicate::*};

#[cfg_attr(test, automock)]
pub trait CsvProcessor {
    fn process_csv_file(&self, input: String) -> Result<String, Box<dyn Error>>;
}

#[cfg(test)]
pub fn get_mock() -> MockCsvProcessor {
    let mut mock = MockCsvProcessor::new();
    mock.expect_process_csv_file()
        .returning(|_file_path| Ok("real".to_string()));
    mock
}

and in src/main.rs:

mod helpers {
    pub(super) mod csv_processor;
}

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    Ok(())
}

#[cfg(test)]
mod main_unit_tests {
    use crate::helpers::csv_processor::{get_mock, CsvProcessor};

    #[test]
    fn testcsv() {
        let hardcoded_file_path = "./csv/example_file.csv".to_string();

        let mock = get_mock();

        match mock.process_csv_file(hardcoded_file_path) {
            Ok(result) => println!("CSV file processed successfully: {}", result),
            Err(e) => eprintln!("Error: {}", e),
        }
    }
}

@hax10 According to the docs you can mock an entire module. That is what I'm trying to do

Where does it say that?

Edit: Never tried this before, but you can apparently. Requires nightly. There is sample code here:

Seems pretty gross and hideously awful disgusting horrendous syntax to me that Rust forces you to use some jank OOP "trait" construct just to be able to mock a single function. :cry:

@tbfleming so your solution is to just not unit test anything more than a simple function with no dependencies?

Lol, good luck with that

It's a mismatch in POV which seems to happens frequently with people coming from managed languages. It's absolutely possible to write functions so that they are composable and testable without requiring mocking. You have just have to shift your POV on the problem. The same kind of shift required by inversion of dependency but in another dimension still. That's the best I can explain.

Generally (but not always) it entails pushing the side effectful code to the very root of your program. So that every branch can be tested as pure-ish functions.

4 Likes

must be nice to work for someone who will accept this kind of half tested code! haha

Like I said before, you can choose to move the "easy things" out to other files and only test them, but you will always end up with one giant untested glue code main.rs which I would like to avoid.

If you have an actual fully tested Rust project please share it.

Please try to not be dismissive, glib and pithy. I'm only trying to help you get another perspective on the problem you have.

6 Likes

Provide a repo or it doesn’t exist

@tbfleming thanks?

I don't see how this other untested C++ codebase proves anything. Nor is it even relevant to original question of how to mock a function. I guess it's better than no replies at all. maybe?

You're being ornery so I'll give up and be ornery. I don't see how you could look at the tests in that repo and say it's untested.

4 Likes

Actually, it no longer requires nightly to do this, as of Mockall 0.9.0. Sorry that I left that line in the docs, but it's fixed in the current master branch.

1 Like

Um, it doesn't. I don't know exactly what @tbfleming said that caused you to respond this way (he deleted his post, FTR), but Rust doesn't force anybody to use traits if they want to use mocking.

Traits do happen to be the easiest way to use mocking, because they don't require any changes to the code under test. That's why many people who roll their own mocking solutions use them. But Mockall's approach is to use compile-time substitution to minimize changes to the code under test. Usually, it can be done with a single call to #[double]. And that works when mocking traits, structs, or modules.
The reason that Mockall can't mock a single free-standing function isn't because of some limitation in the Rust language; it's actually just a matter of namespacing. If you were to do #[automock] fn myfunction(), then what would the mock function be called? mock_myfunction? And would you call mock_myfunction_context() to set up its expectations? The number of global symbols would grow quickly as you mock multiple methods. The requirement to put mockable functions into a module really just serves to limit the number of new global symbols: one per module.

5 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.