lundi 6 janvier 2020

What patterns exist for mocking a single function while testing? [duplicate]

I have a function generates a salted hash digest for some data. For the salt, it uses a random u32 value. It looks something like this:

use rand::RngCore;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;

fn hash(msg: &str) -> String {
    let salt = rand::thread_rng().next_u32();

    let mut s = DefaultHasher::new();
    s.write_u32(salt);
    s.write(msg.as_bytes());
    format!("{:x}{:x}", &salt, s.finish())
}

In a test, I'd like to validate that it produces expected values, given a known salt and string. How do I mock (swizzle?) rand::thread_rng().next_u32() in the test to generate a specific value? In other words, what could replace the comment in this example to make the test pass?

mod tests {
    #[test]
    fn test_hashes() {
        // XXX How to mock ThreadRng::next_u32() to return 3892864592?
        assert_eq!(hash("foo"), "e80866501cdda8af09a0a656");
    }
}

Some approaches I've looked at:

  • I'm aware that the ThreadRng returned by rand::thread_rng() implements RngCore, so in theory I could set a variable somewhere to store a reference to a RngCore, and implement my own mocked variant to set during testing. I've taken this sort of approach in Go and Java, but I couldn't get the Rust type checker to allow it.

  • I looked at the list of mock frameworks, such as MockAll, but they appear to be designed to mock a struct or trait to pass to a method, and this code doesn't pass one, and I wouldn't necessarily want users of the library to be able to pass in a RngCore.

  • Use the #[cfg(test)] macro to call a different function specified in the tests module, then have that function read the value to return from elsewhere. This I got to work, but had to use an unsafe mutable static variable to set the value for the mocked method to find, which seems gross. Is there a better way?

As a reference, I'll post an answer using the #[cfg(test)] + unsafe mutable static variable technique, but hope there's a more straightforward way to do this sort of thing.

Aucun commentaire:

Enregistrer un commentaire