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();