Skip to content
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

Open
e00E opened this issue Aug 31, 2020 · 10 comments
Open

Allow expectation on async trait function to return a future #189

e00E opened this issue Aug 31, 2020 · 10 comments
Labels
enhancement New feature or request

Comments

@e00E
Copy link

e00E commented Aug 31, 2020

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:

#[mockall::automock]
#[async_trait::async_trait]
trait T {
    async fn foo(&self) -> u32;
}

fn main() {
    let mut t = MockT::new();
    t.expect_foo().returning(|| 1);
    // These two don't work:
    t.expect_foo().returning(|| async { 1 });
    t.expect_foo()
        .returning(|| async { futures::future::pending().await });
}

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.

@asomers
Copy link
Owner

asomers commented Aug 31, 2020

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 cargo expand to help understand what the async_trait is doing, so you'll know exactly how to write the function's signature.

mock! {
    T {
        async fn always_immediately_ready(&self) -> i32;
        fn might_not_be_ready(&self) -> Future<i32>;
    }
}

@e00E
Copy link
Author

e00E commented Aug 31, 2020

I see, I can keep using async fn in the trait (this is important to me) and then use mock! with the different return type instead of automock. In my example this looks like this

#[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.

@asomers
Copy link
Owner

asomers commented Aug 31, 2020

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.

@e00E
Copy link
Author

e00E commented Aug 31, 2020

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 select on them). In order to test this I need the futures to sometimes be pending. The futures come calls to a mockable trait.

@alexlapa
Copy link

alexlapa commented Sep 8, 2020

You might also just try to change #[automock] and #[async_trait] order, so #[async_trait] expands first.

#[async_trait::async_trait]
#[mockall::automock]
trait T {
    async fn foo(&self) -> u32;
}

@fmassot
Copy link

fmassot commented Nov 10, 2022

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.

@Plebshot
Copy link

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.

@shepmaster
Copy link

shepmaster commented Jan 18, 2024

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 Pin<Box<dyn Future>> by changing your trait declaration to return impl Future:

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 async (for now?) and use the mock! macro to return impl Trait:

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);
}
error: method `demo` should be async because the method from the trait is async
  --> src/main.rs:11:1
   |
6  |       async fn demo(&self) -> i32;
   |       ---------------------------- required because the trait method is async
...
11 | / mock! {
12 | |     Example {}
13 | |
14 | |     impl Example for Example {
15 | |         fn demo(&self) -> impl Future<Output = i32>;
16 | |     }
17 | | }
   | |_^
   |
   = note: this error originates in the macro `mock` (in Nightly builds, run with -Z macro-backtrace for more info)

In addition to all that, it'd be nice if mockall performed the pin-boxing for us. That is, if returning was defined something like

fn returning(&mut self, v: impl Future) {
    use_this_somehow(Box::pin(v))
}

@baxterjo
Copy link

baxterjo commented Jul 8, 2024

Is there any movement on this? I would love a solution for my use case

@baxterjo
Copy link

baxterjo commented Jul 9, 2024

I see, I can keep using async fn in the trait (this is important to me) and then use mock! with the different return type instead of automock. In my example this looks like this

#[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.

Adding to this, this will also work in Rust 1.75 without the need for async_trait. Here's my implementation in case anyone new swings by this issue and needs a fix. It uses a combination of some of the solutions on this page as well as the recommended practices in the Rust Blog entry on async traits

#[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 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants