digital-society-coop / axum-sqlx-tx

Request-scoped sqlx transactions for axum
MIT License
45 stars 11 forks source link

Testing Using "type Tx = axum_sqlx_tx::Tx<sqlx::Postgres>;" #38

Closed dave42w closed 3 months ago

dave42w commented 5 months ago

I've just started writing a crate axum-tenancy which uses axum-sql-tx

However, I'm not sure how my test code gets a Tx to pass to my db functions that I call with a Tx from my handlers. My naive pool.begin doesn't compile:

#[cfg(test)]

#[sqlx::test]
async fn insert_duplicate_check(pool: PgPool) -> sqlx::Result<(), sqlx::Error> {

    // how do I get a Tx = axum_sqlx_tx::Tx<sqlx::Postgres> out of a PgPool?
    let t = pool.begin().await.unwrap; // returns a Transaction<'_, Postgres> instead of Tx<Postgres>

    // should be able to insert a user but not insert a duplicate user
    assert_eq!(insert(t, "Dave", "Dave Warnock", true, "dwarnock@test.com", "01234567891").await.is_ok(), true);
    assert_eq!(insert(t, "Dave", "Dave Warnock", true, "dwarnock@test.com", "01234567891").await.is_err(), true);
    Ok(())
}

For info all my db functions take a Tx argument eg

async fn insert(
    mut tx: Tx,
    user_name: &str,
    display_name: &str,
    is_admin: bool,
    email: &str,
    mobile_phone: &str,
) -> Result<(), Error> {

that means I can safely combine them in multiple ways from handlers (eg from a single handler call the insert functions for user, tenant and user-tenant).

By always using a Tx my functions can be simpler, they don't need to worry about different types of Executor (pool, connection, transaction) which also reduces testing (I only need to test them with a transaction).

connec commented 3 months ago

Tx is designed as an extractor, whose only constructor is from_request_parts.

I'd suggest you update your db functions to take a Transaction<'_, Postgres> and when you call them you can pass &mut *tx to invoke DerefMut, which will give you a Transaction<'_, _>.

async fn insert(
    tx: &mut Transaction<'_, Postgres>,
    user_name: &str,
    display_name: &str,
    is_admin: bool,
    email: &str,
    mobile_phone: &str,
) -> Result<(), Error> {
#[sqlx::test]
async fn insert_duplicate_check(pool: PgPool) -> sqlx::Result<(), sqlx::Error> {
    let mut t = pool.begin().await.unwrap;

    // should be able to insert a user but not insert a duplicate user
    assert_eq!(insert(&mut t, "Dave", "Dave Warnock", true, "dwarnock@test.com", "01234567891").await.is_ok(), true);
    assert_eq!(insert(&mut t, "Dave", "Dave Warnock", true, "dwarnock@test.com", "01234567891").await.is_err(), true);
    Ok(())
}
// some handler
async function handler(mut tx: Tx) -> impl IntoResponse {
  let result = insert(&mut *tx, /* ... */).await;
  // ...
}