[rust] DB integration tests with Rocket + Diesel
Using Diesel with Rocket usually require the use of rocket_sync_db_pools
crate which hides the complicated setup of initializing a DB connection pool and expose it to handlers via a opaque type that you can call run
on to get a Future back and only in that Future you will get a connection.
This makes writing a integration test with a Rocket handler a bit more complicated because the impl of the DB Pool guard type is generated on-the-fly and not bind to a trait, so we can't just write a mock implementation of it.
After some trial and error, I realized that you can initialize Rocket to the Ignite
state (initialized but not launched/listening yet). We generate a new test DB on-the-fly for every test so we also need to config the Rocket instance to use the right test-specific DB url. Here's the code doing so:
pub async fn rocket_test<F, Fut, R>(&self, f: F) -> anyhow::Result<R>
where
F: FnOnce(DbConnection) -> Fut,
Fut: Future<Output = R> + Send + 'static,
R: Send + 'static,
{
let figment = rocket::Config::figment()
.merge(("databases.db.url", self.db_url.clone()));
let rocket = rocket::custom(figment)
.attach(DbConnection::fairing())
.ignite()
.await?;
let conn = DbConnection::get_one(&rocket)
.await
.ok_or(MyError::DbError(
"unable to get db connection".to_string(),
))?;
let result = f(conn).await;
rocket.shutdown().notify();
Ok(result)
}
and therefore, you can write tests as such:
rocket_test(async move |conn| {
let ret = a_rocket_handler(
conn,
params,
)
.await
.expect_err("handler should fail");
assert_eq!(
ret,
MyError::BadInputError("fail".to_string()),
);
})
.await
.unwrap();