-
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 expectation on async trait function to return a future #189
Comments
If you know that you will need set set expectations that aren't immediately ready, write the method as a normal method returning a future rather than an async method. Can you use mock! {
T {
async fn always_immediately_ready(&self) -> i32;
fn might_not_be_ready(&self) -> Future<i32>;
}
} |
I see, I can keep using #[async_trait::async_trait]
trait T {
async fn foo(&self) -> u32;
}
mockall::mock! {
T {
fn foo(&self) -> impl std::future::Future<Output = u32>;
}
}
fn main() {
let mut t = MockT::new();
t.expect_foo().returning(|| Box::new(async { 1 }));
} This works. Thank you! If you would like I could make a PR to add an example like this to the docs. |
Glad it works! I think better docs would help here. However, I'd like to see a more realistic use first. Fortunately, I expect to soon have the good fortune of using Mockall with an async trait myself, soon. But it might be a few more weeks before I get to it. |
The realistic use I have is that I want to test the behavior of a piece of code that has multiple futures running at the same time (calling |
You might also just try to change #[async_trait::async_trait]
#[mockall::automock]
trait T {
async fn foo(&self) -> u32;
} |
Just to give an example of another use case: during testing, it could be useful to delay a response to trigger a timeout that must be handled correctly. And I want to test this behaviour. |
I've also recently came across the problem of testing timeouts in particular. While I found a workaround for my particular case by triggering some retry logic in the application itself, this could definitely be useful. |
Now that Rust 1.75 is released, the same limitations exist with the native implementation of async functions in traits, plus an additional one. You can return a synchronous value when following the happy path: use mockall::{*, predicate::*};
#[automock]
trait Example {
async fn demo(&self) -> i32;
}
async fn usage(x: impl Example) -> i32 {
x.demo().await + 1
}
#[tokio::main]
async fn main() {
let mut mock = MockExample::new();
mock.expect_demo().times(1).returning(|| 42);
assert_eq!(43, usage(mock).await);
} You can return a use mockall::{predicate::*, *};
use std::future::Future;
#[automock]
trait Example {
fn demo(&self) -> impl Future<Output = i32>;
}
async fn usage(x: impl Example) -> i32 {
x.demo().await + 1
}
#[tokio::main]
async fn main() {
let mut mock = MockExample::new();
mock.expect_demo()
.times(1)
.returning(|| Box::pin(async { 42 }));
assert_eq!(43, usage(mock).await);
} However, you cannot have the trait method be use mockall::{predicate::*, *};
use std::future::Future;
trait Example {
async fn demo(&self) -> i32;
}
mock! {
Example {}
impl Example for Example {
fn demo(&self) -> impl Future<Output = i32>;
}
}
async fn usage(x: impl Example) -> i32 {
x.demo().await + 1
}
#[tokio::main]
async fn main() {
let mut mock = MockExample::new();
mock.expect_demo()
.times(1)
.returning(|| Box::pin(async { 42 }));
assert_eq!(43, usage(mock).await);
}
In addition to all that, it'd be nice if mockall performed the pin-boxing for us. That is, if fn returning(&mut self, v: impl Future) {
use_this_somehow(Box::pin(v))
} |
Is there any movement on this? I would love a solution for my use case |
Adding to this, this will also work in Rust 1.75 without the need for #[trait_variant::make(MyTrait: Send)]
#[cfg_attr(test, automock)]
pub trait LocalMyTrait {
async fn trait_method(
&mut self,
arg_1: String,
arg_2: u8,
) -> anyhow::Result<()>;
}
// Actual struct
#[derive(Clone, Debug)]
pub struct MyStruct {
member_1: u64,
}
// Actual trait impl for struct.
impl MyTrait for MyStruct {
async fn trait_method(
&mut self,
arg_1: String,
arg_2: u8,
) -> anyhow::Result<()> {
// Do some async stuff here
Ok(())
}
}
#[cfg(test)]
mock! {
pub MyStruct {}
impl Clone for MyStruct {
fn clone(&self) -> Self;
}
impl MyTrait for MyStruct {
// This implementation of the mock trait method is required to allow the mock methods to return a future.
fn trait_method(
&mut self,
arg_1: String,
arg_2: u8,
)-> impl std::future::Future<Output=anyhow::Result<()>> + Send;
}
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn test_mock_trait_method_can_sleep() {
let mut mock_struct = MyStruct::default();
mock_struct
.expect_trait_method()
.times(1)
.returning(|arg_1, arg_2| {
// Any access to args must be done before async block because the lifetime of the async block could be longer than the lifetime of the borrowed arguments.
Box::pin(async {
tokio::time::sleep(Duration::from_secs(5)).await;
Ok(())
})
});
mock_struct.trait_method("hello_world".to_string(), 5).await.unwrap();
}
} Apologies if there are any syntax errors, I wrote this in the github comment editor 😆 |
I saw in the changelog that you implemented support for async_trait. Thank you!
I noticed a limitation in this implementation. In the expectation we have to return a non future value. This prevents some advanced mocking from working:
This is an observable difference because async functions can either return their value or be pending. By only being able to return the values we cannot mock and test pending behavior. In the
async { 1 }
you can imagine that we want to be pending first for a short time (like sleeping, or waiting for a signal) and only then return the value. Or we get the value from an async channel.These types of tests are probably rare and the current functionality is correct for most users but it might be nice to have. From an api perspective the most general solution is to have
expect_foo
always return a future so you would need to start an async block in the closure. I understand this is a little more work for users that don't need this but it is more correct.Alternatively there could be another postfix like
returning_async
but I'm not sure how this meshes with the existing_st
.Or maybe mockall could detect whether
returning
on an async trait function expectation returns a normal value or a future. I don't know whether this is possible.The text was updated successfully, but these errors were encountered: