asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

Allow mocking some methods with generic non-static arguments #408

Closed asomers closed 2 years ago

asomers commented 2 years ago

Add a #[mockall::concretize] attribute. When set on a function or method, its generic expectations will be turned into trait objects.

But it only works for function arguments that are pure generic types or a few basic combinations:

Issue #217

cjriches commented 1 year ago

This seems great and I'd love to use it, but unless I'm doing something very wrong, it doesn't seem to work if mockall is a dev-dependency.

Specifically, the problem is that #[mockall::concretize] only seems to take effect when used directly, and #[cfg_attr(test, mockall::concretize)] has no effect. This means that mockall must be present in non-test builds, which is far from ideal.

I'm guessing this is something to do with the following line in the docs, suggesting that the attribute is more of a textual marker than taking effect directly:

NB: This attribute must be imported with its canonical name.  It won't work otherwise!

Is this fixable?

asomers commented 1 year ago

Hm, you must be doing something wrong, because I use concretize with mockall as a dev-dependency. The warning you mention isn't related. It just means, don't do something like import mockall::concretize as something_else.

cjriches commented 1 year ago

Let me provide an example, and hopefully you can tell me what I'm doing wrong.

The following is based on the example in the docstring:

use std::path::Path;

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

#[cfg_attr(test, automock)]
trait Foo {
    #[concretize]
    fn foo<P: AsRef<Path>>(&self, p: P);
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test() {
        let mut mock = MockFoo::new();
        mock.expect_foo()
            .withf(|p| p.as_ref() == Path::new("/tmp"))
            .return_const(());
        mock.foo(Path::new("/tmp"));
    }
}

This works fine in test mode, but cargo check errors out because #[concretize] isn't defined. If I simply change #[concretize] to #[cfg_attr(test, concretize)], then it compiles fine in normal mode, but the following errors spew out in test mode:

error[E0658]: attributes on expressions are experimental                                                                             [0/464] --> src/main.rs:8:22
  |
8 |     #[cfg_attr(test, concretize)]
  |                      ^^^^^^^^^^
  |
  = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information

error: expected non-macro attribute, found attribute macro `concretize`
 --> src/main.rs:8:22
  |
8 |     #[cfg_attr(test, concretize)]
  |                      ^^^^^^^^^^ not a non-macro attribute

error[E0658]: custom attributes cannot be applied to expressions
 --> src/main.rs:8:22
  |
8 |     #[cfg_attr(test, concretize)]
  |                      ^^^^^^^^^^
  |
  = note: see issue #54727 <https://github.com/rust-lang/rust/issues/54727> for more information

error[E0310]: the parameter type `P` may not live long enough
    --> src/main.rs:6:18
     |
6    | #[cfg_attr(test, automock)]
     |                  ^^^^^^^^ ...so that the type `Expectations<P>` will meet its required lifetime bounds...
     |
note: ...that is required by this bound
    --> /home/cjriches/.cargo/git/checkouts/mockall-17cf93ce910ebbb5/b51cb30/mockall/src/lib.rs:1418:29
     |
1418 | pub trait AnyExpectations : Any + Send + Sync {}
     |                             ^^^
     = note: this error originates in the attribute macro `automock` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider adding an explicit lifetime bound...
     |
9    |     fn foo<P: AsRef<Path>> + 'static(&self, p: P);
     |                            +++++++++

error[E0310]: the parameter type `P` may not live long enough
    --> src/main.rs:6:18
     |
6    | #[cfg_attr(test, automock)]
     |                  ^^^^^^^^ ...so that the type `P` will meet its required lifetime bounds...
     |
note: ...that is required by this bound
    --> /home/cjriches/.cargo/git/checkouts/mockall-17cf93ce910ebbb5/b51cb30/mockall/src/lib.rs:1418:29
     |
1418 | pub trait AnyExpectations : Any + Send + Sync {}
     |                             ^^^
     = note: this error originates in the attribute macro `automock` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider adding an explicit lifetime bound...
     |
9    |     fn foo<P: AsRef<Path>> + 'static(&self, p: P);
     |                            +++++++++

The last two errors seem to just be the effects of concretize not happening, but the first few are more worrying.

How do I get concretize to compile under both normal and test mode?

asomers commented 1 year ago

Yep, it's a bug. I never noticed because I've only used concretize with mock!, not #[automock]. Moving discussion to https://github.com/asomers/mockall/issues/427 .