How to mock specific methods but not all of them in Rust?

后端 未结 2 1890
野性不改
野性不改 2020-12-06 23:56

I have troubles figuring out unit tests for the methods of the target struct.

I have a method random_number that returns a random value based on the att

相关标签:
2条回答
  • 2020-12-07 00:38

    Thanks to @Shepmaster I came to this workaround. I have added the actual Rng to have more context.

    use rand::{thread_rng, Rng}; // 0.6.5
    
    struct RngTest(Vec<u64>);
    
    impl RngTest {
        fn random_number(&self) -> u64 {
            let random_value = thread_rng().choose(&self.0);
            *random_value.unwrap()
        }
    
        fn plus_one(&self) -> u64 {
            self.random_number() + 1
        }
    }
    
    #[test]
    fn plus_one_works() {
        let rng = RngTest(vec![1]);
        assert_eq!(rng.plus_one(), 2);
    }
    

    I can set an appropriate value in the object and don't need to use traits. There is a downside though - this forces me to have a special instance of my type for this specific test which I would like to avoid because my actual type has a lot of fields and I wanted to define its creation once for all the tests using speculate.

    0 讨论(0)
  • 2020-12-07 00:51

    How to mock specific methods but not all of them in Rust?

    As you have already learned, you cannot replace methods on a type. The only thing you can do is move the methods to a trait and then provide production and test-specific implementations of that trait. How you structure the trait determines the granularity of what you are able to test.

    Trait with a default implementation

    Depending on your use case, you might be able to use a default implementation:

    trait SomeRng {
        fn random_number(&self) -> u64;
    
        fn plus_one(&self) -> u64 {
            self.random_number() + 1
        }
    }
    
    struct RngTest(u64);
    impl SomeRng for RngTest {
        fn random_number(&self) -> u64 {
            self.0
        }
    }
    
    #[test]
    fn plus_one_works() {
        let rng = RngTest(41);
        assert_eq!(rng.plus_one(), 42);
    }
    

    Here, random_number is a required method, but plus_one has a default implementation. Implementing random_number gives you plus_one by default. You could also choose to implement plus_one if you could do it more efficiently.

    What does the real rand crate do?

    The real rand crate uses two traits:

    • Rng

      pub trait Rng: RngCore { /* ... */ }
      

      An automatically-implemented extension trait on RngCore providing high-level generic methods for sampling values and other convenience methods.

    • RngCore

      pub trait RngCore { /* ... */ }
      

      The core of a random number generator.

    This splits the core interesting parts of the implementation from the helper methods. You can then control the core and test the helpers:

    trait SomeRngCore {
        fn random_number(&self) -> u64;
    }
    
    trait SomeRng: SomeRngCore {
        fn plus_one(&self) -> u64 {
            self.random_number() + 1
        }
    }
    
    impl<R: SomeRngCore> SomeRng for R {}
    
    struct RngTest(u64);
    impl SomeRngCore for RngTest {
        fn random_number(&self) -> u64 {
            self.0
        }
    }
    
    #[test]
    fn plus_one_works() {
        let rng = RngTest(41);
        assert_eq!(rng.plus_one(), 42);
    }
    
    0 讨论(0)
提交回复
热议问题