-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow mocking methods with non-static generic arguments #217
Comments
Hello! Any breakthrough on this issue? Not being able to have non-static generic methods is a very stong design constraint. I would like to help, if you can guide me a bit :) Also, maybe it could be nice to be able to ignore the methods that we are unable to mock, with a #[automock]
trait Foo {
#[ignore]
fn foo<T: Debug>(&self, t: T);
} Calls to such trait methods would panic. |
No, I haven't had any time to work on it. And as for your |
Thanks for the answer! |
I hit what I think is an instance of this. Here's the minimal example:
Here's a bigger and more motivating example. I'm using arenas, so the lifetime of the returned value is naturally bounded by the lifetime of the arena parameter.
I wish I could |
@ljw1004 your problem is not quite the same, because your only generic parameters are lifetimes. In your case, I think you should be able to simply eliminate the lifetime parameter. What happens if you do that? If that's not an option, then an easy workaround is to use |
Update: I've made some progress on this feature. I have a branch that allows mocking functions with non-static generic parameters, with some restrictions:
The last restriction is the most troubling. I'm not sure it's worth merging this feature if I can't solve it. Here's an example of how the new feature will work: #[automock]
trait Foo {
#[mockall::concrete_expectations]
fn foo<P: AsRef<Path>>(&self, p: P);
}
#[test]
fn test_foo() {
let mut foo = MockFoo::new();
foo.expect_foo()
.withf(|p| p.as_ref() == Path::new("/tmp"))
.times(3)
.return_const(());
foo.foo(Path::new("/tmp"));
foo.foo(Path::new("/tmp").to_owned());
foo.foo("/tmp");
} |
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: * T * &T * &mut T * &[T] Issue #217
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: * T * &T * &mut T * &[T] Issue #217
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: * T * &T * &mut T * &[T] Issue #217
Hi @asomers , this looks promising. But the solution you provided doesn't work for a generic method with generic return type. Please, let me know if I'm mistaken or if there is any work around. |
|
Just got bit by this. Wanted to pass a |
I was trying to return a non-static generic type and it is giving me error.
for return type it is saying |
That's exactly what concretize should help with. Have you tried it? |
Yeah, |
Hi @asomers , that is not the only issue if you wrap the generic type in another Type, it gives the same error. Even in case of arguments.
In above example, it is unable to find |
Yeah, it doesn't seem to work: use async_trait::async_trait;
use serde::DeserializeOwned;
#[async_trait]
pub trait Coll<T>: Send + Sync
where
T: DeserializeOwned + Send + Sync + Unpin + 'static,
{
async fn insert_many<D: IntoIterator<Item = T> + Send, O: Into<Option<()>> + Send + 'static>(
&self,
_docs: D,
_options: O,
) -> Result<(), ()>;
}
mockall::mock! {
#[derive(Debug)]
pub Collection<T: DeserializeOwned + Send + Sync + Unpin + 'static> {}
#[async_trait]
impl<T: DeserializeOwned + Send + Sync + Unpin + 'static> Coll<T> for Collection<T> {
#[mockall::concretize]
async fn insert_many<
D: IntoIterator<Item = T> + Send,
O: Into<Option<()>> + Send + 'static,
>(
&self,
_docs: D,
_options: O,
) -> Result<(), ()>;
}
} which errors:
At first I thought this was related to your comment:
But it sounds different? Compiles fine when D is +'static, but that's not what I want here |
@TheDan64 sorry I missed this when you posted it. Could you try again, but using mockall's "nightly" feature? That gives much better error messages. |
Getting the same error as @TheDan64. Seems to be related to Cargo.toml (0d27b44) mockall = { git = "https://github.com/asomers/mockall", features = ["nightly"] } // src/main.rs
use mockall::predicate::*;
use mockall::*;
#[automock]
trait MyTrait {
#[concretize]
fn foo<K: AsRef<[u8]>>(&self, x: K) -> u32; // works
#[concretize]
fn foo2<K: Send + AsRef<[u8]>>(&self, x: K) -> u32; // breaks
}
fn call_with_four(x: &mut MockMyTrait) -> u32 {
x.foo(vec![1, 2, 3, 4]) + x.foo2(vec![1, 2, 3, 4])
}
fn main() {
let mut mock = MockMyTrait::new();
mock.expect_foo()
.times(1)
.returning(|x: &dyn AsRef<[u8]>| x.as_ref().len() as u32 + 1);
assert_eq!(10, call_with_four(&mut mock));
} Error: ~/code/tmp/mocktmp ❯ cargo +nightly run
Compiling mocktmp v0.1.0 (/Users/maxime/Code/tmp/mocktmp)
error: Type cannot be made into a trait object
--> src/main.rs:10:16
|
10 | fn foo2<K: Send + AsRef<[u8]>>(&self, x: K) -> u32; // breaks
| ^^^^^^^^^^^^^^^^^^
error[E0412]: cannot find type `K` in this scope
--> src/main.rs:10:46
|
10 | fn foo2<K: Send + AsRef<[u8]>>(&self, x: K) -> u32; // breaks
| ^ not found in this scope
For more information about this error, try `rustc --explain E0412`.
error: could not compile `mocktmp` (bin "mocktmp") due to 2 previous errors |
That's because #[concretize]
fn foo2<K: AsRef<[u8]> + Send>(&self, x: K) -> u32; or if that doesn't work, trait MyTrait2: AsRef<[u8]> + Send {}
...
#[concretize]
fn foo2<K: MyTrait2>(&self, x: K) -> u32; |
(Your first code block is very similar to mine, with AsRef/Send swapped – and fails with the same error. Typo?) The trait alias seems to work, but from what I understand, it requires implementing
In my case, I didn't actually need the |
Currently Mockall cannot mock generic methods with non-static generic arguments. The reason is because arguments are downcast to
Any
and stored in a map. That allows the caller to set different expectations for different types, like this:However, it also poses some problems. Chiefly, it's impossible to mock methods with non-
'static
generic arguments, because they can't be downcast. Also, it's difficult to mock arguments with unnameable or difficult-to-name argument types, like closures or futures.An alternative may be possible: mock methods with generic arguments by turning those arguments into trait objects. That would skip downcasting. Most
returning
closures would be compatible; the argument's type would just change from concrete like&u32
to a trait object, like&dyn Debug
. Also, theexpect_foo
method would no longer be generic. All expectations would be matched against all invocations.There are other problems with this approach, however. Chiefly, not every type can be made into a trait object. Also, most trait objects wouldn't be useable with any of the
predicate
types. i.e., you wouldn't be able to doexpect_foo().with(predicate::eq(42))
. So I don't think this new technique could be made mandatory. Instead, it would probably have to be optional, opt-in with an attribute. Like this:And of course, none of this applies to generic return values, only arguments.
The text was updated successfully, but these errors were encountered: