kriomant/mockers

feature request: Mocking structures

asomers opened this issue · 9 comments

The user's guide suggests a workaround for mocking structures: turn the struct into a trait while in #[cfg(test)]. This only works if the mock object is accessed exclusively by reference, or if it can be turned into a trait object. However, some traits cannot be made into trait objects. For example, traits with generic methods cannot be made into trait objects. Would it be possible to extend Mockers to support mocking structures directly? Mock-it, Pseudo, and Simulacrum can do it. Here's some example code using Pseudo:

use pseudo::Mock;

struct Bean();
impl Bean {
    pub fn eat(&self) {}
}
struct BeanMock{
    eat: Mock<(), ()>
}
impl BeanMock {
    pub fn eat(&self) {
        self.eat.call(())
    }
}

#[cfg(test)]
mod t {

    use pseudo::Mock;
    use test_double::*;
    #[test_double] use super::Bean;

    #[test]
    fn mock_struct() {
        let mock = Bean{eat: Mock::default()};
        mock.eat();
    }
}

Another use case for mocking structs: returning "impl Trait" is not allowed in a trait method. So a struct with a method that returns "impl Trait" cannot be turned into a trait as in the suggested workaround.

Use can use mockers same way you use pseudo:

struct Bean();
impl Bean {
    pub fn eat(&self) {}
}

#[mocked("BeanMock")]
trait BeanTrait {
    fn eat(&self);
}

#[cfg(test)]
mod t {
    use test_double::*;
    #[test_double] use super::Bean;

    #[test]
    fn mock_struct() {
        let scenario = Scenario::new();
        let mock = scenario.create_mock::<BeanMock>();
        mock.eat();
    }
}

But I can add support for #[mocked] impl Bean { ... } to avoid unnecessary method signatures copying.

Hi @kriomant I'm playing around with your example, but as far I can see is not being possible to use the mocked trait in a different file. This is how my test looks like:

#[test]
fn mock_struct() {
        let scenario = Scenario::new();
        let mock = scenario.create_mock::<Bean>();
        scenario.expect(mock.eat_call().and_return("fake".to_string()));
        assert_eq!(mock.eat(), "fake");
}

And Bean and BeanMock is implemented in another file:

#[cfg(test)] use mockers_derive::mocked;


pub struct Bean {
    name: String,
}

impl Bean {
    pub fn eat(&self) -> String {
        "original".to_owned()
    }
}

#[mocked(BeanMock)]
pub trait BeanTrait {
    fn eat(&self) -> String;
}

For a test inside the file above, it works, but for the test in another file, I got the following error and I'm still trying to understand what is happening:

error[E0599]: no method named `eat` found for type `mock::BeanMock` in the current scope
  --> src/user.rs:93:25
   |
93 |         assert_eq!(mock.eat(), "fake");
   |                         ^^^
   | 
  ::: src/mock.rs:14:1
   |
14 | #[mocked(BeanMock)]
   | ------------------- method `eat` not found for this
   |
   = help: items from traits can only be used if the trait is in scope
   = note: the following trait is implemented but not in scope, perhaps add a `use` for it:
           `use crate::mock::BeanTrait;`

In general, what I'm trying to achieve right now is to stub a struct that implements a certain trait. And inside the caller of this struct, I use both struct and functions to get the result that I want. Does make sense this error above or what I'm trying to achieve?

Just add use BeanTrait as _; to file with test.

Thanks. Worked like a charm. One comment about using test_double is that would be nice in somehow to at a function level. my problem was that other methods were using the same dependency so I would have to create a more robust mock that I need in order to fulfil the requirements for the other methods.

Sorry, I don't understand. Can you clarify your question?

@kriomant your solution no longer works with Mockers 0.20.0. It gives the below error. Could you suggest a new syntax:

error: custom attribute panicked
  --> src/t_mockers.rs:24:1
   |
24 | #[mocked("BeanMock")]
   | ^^^^^^^^^^^^^^^^^^^^^
   |
   = help: message: error in mockers: Parsing error: unexpected attribute parameter

error: aborting due to previous error

Use #[mocked(BeanMock)].