2

I'm writing some integration tests for a web service using axum and postgres and have a Database trait (that is passed to axum through its with_state functionality) to be able to use a mock database for some of the tests:

#[async_trait]
pub trait Database {
    async fn create_user(
        &self,
        user_id: &str,
        created_time: &DateTime<Utc>,
    ) -> Result<(), DatabaseError>;
}

For tests, I define a struct that implements this database trait and that can also be given a function that will assert that the data that ends up in the database layer matches what we expect:

#[derive(Clone)]
struct FakeDb {
    check_create_user: fn(user_id: &str, created_time: &DateTime<Utc>) -> (),
}

#[async_trait]
impl Database for FakeDb {
    async fn create_infonode(
        &self,
        user_id: &str,
        created_time: &DateTime<Utc>,
    ) -> Result<(), DatabaseError> {
        (self.check_create_user)(user_id, created_time);
        Ok(())
    }
}

To use this fake DB, I create a FakeDb struct and pass it an anonymous function with the assertions I want checked:

let fake_db = FakeDb {
    check_create_user: |user_id, created_time| {
        assert_eq!(user_id, "dummy_user1");
        assert_eq!(
          created_time,
          &DateTime::parse_from_rfc3339("2023-03-05T15:10:07.123Z").unwrap()
        );
    },
};
// do a request

This works well, but if I don't want to hard-code the date, but instead want to capture a value from the scope, this breaks down:

let now = Utc::now();
let fake_db = FakeDb {
    check_create_user: |user_id, created_time| {
        assert_eq!(user_id, "dummy_user1");
        assert_eq!(created_time, &now);
    },
};
// do a request using `now`
error[E0308]: mismatched types
   --> src/main.rs:177:36
    |
177 |               check_create_user: |user_id, created_time| {
    |  ________________________________^
178 | |                 assert_eq!(node_id, "node1");
179 | |                 assert_eq!(created_time, &now,);
180 | |             },
    | |_____________^ expected fn pointer, found closure
    |
    = note: expected fn pointer `for<'a, 'b> fn(&'a str, &'b DateTime<Utc>)`
                  found closure `[closure@src/main.rs:177:36: 177:87]`
note: closures can only be coerced to `fn` types if they do not capture any variables
   --> src/main.rs:179:43
    |
179 |                 assert_eq!(created_time, &now,);
    |                                           ^^^ `now` captured here

For more information about this error, try `rustc --explain E0308`.

If I follow the error messages, I end up using a dyn Fn as the parameter type:

check_create_user: dyn Fn(&str, &DateTime<Utc>) -> ()

… but since FakeDb needs to be cloneable, this leads to new errors:

error[E0277]: the size for values of type `(dyn for<'a, 'b> Fn(&'a str, &'b DateTime<Utc>) + 'static)` cannot be known at compilation time
  --> src/main.rs:69:14
   |
69 |     #[derive(Clone)]
   |              ^^^^^ doesn't have a size known at compile-time
   |
   = help: within `FakeDb`, the trait `Sized` is not implemented for `(dyn for<'a, 'b> Fn(&'a str, &'d DateTime<Utc>) + 'static)`
note: required because it appears within the type `FakeDb`
  --> src/main.rs:70:12
   |
70 |     struct FakeDb {
   |            ^^^^^^
note: required by a bound in `Clone`
  --> /private/tmp/nix-build-rustc-1.67.1.drv-0/rustc-1.67.1-src/library/core/src/clone.rs:110:18
   |
   = note: required by this bound in `Clone`
   = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.

At this point, I feel like I'm going down a route of (hopefully) unnecessary complexity, and I wonder: Is there a better way to write tests like this in this scenario, or, if not: how can this be made to work?

beta
  • 2,380
  • 21
  • 38
  • 1
    `dyn` objects need to be wrapped inside a `Box<*>` to be stored in a struct. But still, the closure is probably not cloneable.. – Jonas Berlin Mar 07 '23 at 08:17

0 Answers0