asomers / mockall

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

Mocking sqlx requests #379

Closed Kinrany closed 2 years ago

Kinrany commented 2 years ago

Hello. I'm trying to come up with a way to mock sqlx requests in a generic way. Currently I'm stuck trying to mock this Pool trait:

use async_trait::async_trait;
use mockall::automock;

trait Executor<'c>: sqlx::Executor<'c, Database = sqlx::MySql> {}

#[async_trait]
trait Execute {
    type Output;
    async fn execute(&self, ex: impl Executor<'_> + 'async_trait) -> Self::Output;
}

#[automock]
#[async_trait]
trait Pool {
    async fn execute<E>(&self, executable: E) -> E::Output
    where
        E: Execute + Send + Sync;
}

This would allow a single trait to be used to mock all database calls. I could follow up with details.

Is this possible? Are there too many generics and lifetimes?

asomers commented 2 years ago

Well, what error are you seeing?

Kinrany commented 2 years ago
error[E0310]: the parameter type `E` may not live long enough
  --> pilot_core/src/db_traits.rs:17:1
   |
17 | #[automock]
   | ^^^^^^^^^^^ ...so that the type `Expectations<E>` will meet its required lifetime bounds
   |
   = 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...
   |
22 |         E: Execute + Send + Sync + 'static;
   |                                  +++++++++

error[E0310]: the parameter type `E` may not live long enough
  --> pilot_core/src/db_traits.rs:17:1
   |
17 | #[automock]
   | ^^^^^^^^^^^ ...so that the type `E` will meet its required lifetime bounds
   |
   = 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...
   |
22 |         E: Execute + Send + Sync + 'static;
   |                                  +++++++++

The error suggests that I use 'static, but the intended use case relies on E having string slices in it and is therefore not 'static.

I tried adding an explicit lifetime:

#[automock]
#[async_trait]
pub trait Pool {
    async fn execute<E>(&self, executable: E) -> E::Output
    where
        for<'e> E: Execute + Send + Sync + 'e;
}

This results in a more confusing error:

error[E0263]: lifetime name `'e` declared twice in the same scope
  --> pilot_core/src/db_traits.rs:22:13
   |
22 |         for<'e> E: Execute + Send + Sync + 'e;
   |             ^^
   |             |
   |             declared twice
   |             previous declaration here

Using mock! produces the same error.

Making 'e into a generic variable results in an error that suggests that the macro doesn't see the variable:

#[automock]
#[async_trait]
pub trait Pool {
    async fn execute<'e, E>(&self, executable: E) -> E::Output
    where
        E: Execute + Send + Sync + 'e;
}
error[E0261]: use of undeclared lifetime name `'e`
  --> pilot_core/src/db_traits.rs:22:36
   |
22 |         E: Execute + Send + Sync + 'e;
   |                                    ^^ undeclared lifetime
   |
   = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'e` lifetime
   |
22 |         for<'e> E: Execute + Send + Sync + 'e;
   |         +++++++
help: consider introducing lifetime `'e` here
   |
20 |     async fn execute<'e, 'e, E>(&self, executable: E) -> E::Output
   |                      +++

error[E0261]: use of undeclared lifetime name `'e`
  --> pilot_core/src/db_traits.rs:22:36
   |
17 | #[automock]
   |            - lifetime `'e` is missing in item created through this procedural macro
...
22 |         E: Execute + Send + Sync + 'e;
   |                                    ^^ undeclared lifetime
   |
help: consider making the bound lifetime-generic with a new `'e` lifetime
   |
22 |         for<'e> E: Execute + Send + Sync + 'e;
   |         +++++++
help: consider introducing lifetime `'e` here
   |
20 |     async fn execute<'e, 'e, E>(&self, executable: E) -> E::Output
   |                      +++

error[E0310]: the parameter type `E` may not live long enough
  --> pilot_core/src/db_traits.rs:17:1
   |
17 | #[automock]
   | ^^^^^^^^^^^ ...so that the type `E` will meet its required lifetime bounds...
   |
note: ...that is required by this bound
  --> pilot_core/src/db_traits.rs:22:36
   |
22 |         E: Execute + Send + Sync + 'e;
   |                                    ^^
   = 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...
   |
22 |         E: Execute + Send + Sync + 'e + 'static;
   |                                       +++++++++

Does this help?

I'm pretty sure I'm holding it wrong, so thanks a lot for looking into it.

asomers commented 2 years ago

for<'e> E: Execute + Send + Sync + 'e; is basically just a complicated way of saying E: Execute + Send + Sync + 'static;. Did you try that? Because of the way that expectations are stored internally, they really do need to be 'static, with a few exceptions.

Kinrany commented 2 years ago

It does work if I make the type 'static, but it is unnatural because E doesn't need to own values to work, so getting rid of references requires extra cloning.

Oh well. Anyway, thanks for the good crate 🙂