asomers / mockall

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

Mocking an async trait with generics #580

Closed Tockra closed 4 months ago

Tockra commented 5 months ago

Hello,

I still have to use the async_trait crate because I have a trait where I need to create trait objects. This seems to be impossible if the trait contains async methods. So I have this:

#[async_trait]
pub trait DatabaseService<T: model::Element>: Debug + Sync + Send {
    fn as_any(&self) -> &dyn Any;
    async fn connect(&mut self, config: &Config) -> Result<(), mongodb::error::Error>;
    async fn get_documents(
        &self,
        conditions: Conditions
    ) -> Vec<T>;
}

Now I'm having problems using mockall with this. Automock does not work, and I have tried multiple variants of mock!.

Maybe somebody can give me a hint on how I can mock a test for the following function:

pub async fn get_positions(db: &dyn DatabaseService<Position>, filters: Filters) -> Vec<Position> {
    db.get_documents(
        Conditions::from(filters.clone())
    )
    .await
}

I just want to test a few things:

  1. My get_positions function calls the get_documents function of the passed DatabaseService.
  2. My get_positions function returns the result of db.get_documents().

Does anybody have a hint for me on how to achieve this?

Thanks!

asomers commented 5 months ago

automock does work with async_trait. For example, see https://github.com/asomers/mockall/blob/master/mockall/tests/automock_async_trait.rs . The main thing to be aware of is that the automock attribute must precede the async_trait attribute.

Tockra commented 5 months ago

Okay, my program does not compile if I do it. Maybe somebody could assist to fix the example above.

asomers commented 5 months ago

Maybe you could post the error message?

Tockra commented 4 months ago

Of course:

error: future cannot be sent between threads safely
  --> src/db/mod.rs:30:1
   |
30 | #[automock]
   | ^^^^^^^^^^^ future created by async block is not `Send`
   |
note: captured value is not `Send` because `&mut` references cannot be sent unless their referent is `Send`
  --> src/db/mod.rs:45:27
   |
45 |     async fn connect(&mut self, config: &Config) -> Result<(), mongodb::error::Error>;
   |                           ^^^^ has type `&mut MockDatabaseService<T>` which is not `Send`, because `MockDatabaseService<T>` is not `Send`
   = note: required for the cast from `Pin<Box<{async block@src/db/mod.rs:30:1: 30:12}>>` to `Pin<Box<(dyn futures::Future<Output = Result<(), mongodb::error::Error>> + std::marker::Send + 'async_trait)>>`
   = note: this error originates in the attribute macro `automock` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider further restricting this bound
   |
32 | pub trait DatabaseService<T: model::Element + std::marker::Send>: Debug + Sync + Send {
   |                                             +++++++++++++++++++
asomers commented 4 months ago

Did you try following the compiler's advice?

Tockra commented 4 months ago

After fullfilling the suggestions step for step I arrived at:

#[cfg(test)]
use mockall::{automock, mock, predicate::*};
#[cfg_attr(test, automock)]
#[async_trait]
pub trait DatabaseService<T: model::Element + std::marker::Send + std::marker::Sync>:
    Debug + Sync + Send
    // ...

With error message:

error[E0277]: `(dyn std::any::Any + 'static)` cannot be sent between threads safely
   --> src/db/mod.rs:30:11
    |
30  | pub trait DatabaseService<T: model::Element + std::marker::Send + std::marker::Sync>:
    |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn std::any::Any + 'static)` cannot be sent between threads safely
    |
    = help: the trait `std::marker::Send` is not implemented for `(dyn std::any::Any + 'static)`, which is required by `MockDatabaseService<T>: std::marker::Send`
    = note: required for `Unique<(dyn std::any::Any + 'static)>` to implement `std::marker::Send`
note: required because it appears within the type `Box<(dyn std::any::Any + 'static)>`
   --> /Users/titan/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/boxed.rs:197:12
    |
197 | pub struct Box<
    |            ^^^
note: required because it appears within the type `__as_any::Rfunc<T>`
   --> src/db/mod.rs:28:18
    |
28  | #[cfg_attr(test, automock)]
    |                  ^^^^^^^^
note: required because it appears within the type `__as_any::Expectation<T>`
   --> src/db/mod.rs:28:18
    |
28  | #[cfg_attr(test, automock)]
    |                  ^^^^^^^^
    = note: required for `Unique<__as_any::Expectation<T>>` to implement `std::marker::Send`
note: required because it appears within the type `alloc::raw_vec::RawVec<__as_any::Expectation<T>>`
   --> /Users/titan/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/raw_vec.rs:69:19
    |
69  | pub(crate) struct RawVec<T, A: Allocator = Global> {
    |                   ^^^^^^
note: required because it appears within the type `Vec<__as_any::Expectation<T>>`
   --> /Users/titan/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:398:12
    |
398 | pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    |            ^^^
note: required because it appears within the type `__as_any::Expectations<T>`
   --> src/db/mod.rs:28:18
    |
28  | #[cfg_attr(test, automock)]
    |                  ^^^^^^^^
note: required because it appears within the type `MockDatabaseService_DatabaseService_13065831140148901594<T>`
   --> src/db/mod.rs:30:11
    |
30  | pub trait DatabaseService<T: model::Element + std::marker::Send + std::marker::Sync>:
    |           ^^^^^^^^^^^^^^^
note: required because it appears within the type `MockDatabaseService<T>`
   --> src/db/mod.rs:30:11
    |
30  | pub trait DatabaseService<T: model::Element + std::marker::Send + std::marker::Sync>:
    |           ^^^^^^^^^^^^^^^
note: required by a bound in `DatabaseService`
   --> src/db/mod.rs:31:20
    |
30  | pub trait DatabaseService<T: model::Element + std::marker::Send + std::marker::Sync>:
    |           --------------- required by a bound in this trait
31  |     Debug + Sync + Send
    |                    ^^^^ required by this bound in `DatabaseService`
    = note: this error originates in the attribute macro `automock` (in Nightly builds, run with -Z macro-backtrace for more info)
asomers commented 4 months ago

That message is telling you that the as_any method's return value is not Send. Try adding a + Send trait bound to it.