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?