eyecatch

Rust sqlx::test がユニットテストに便利だった。テストDBを自動で用意。

Posted on 2024/06/01
# Technology

sqlx::test について

ウェブ開発をしているとリポジトリ層のテストはDIやテスト用のDBを必要とすることがあるため、めんどくさい時があります。sqlxはテストのためにsqlx::test Attribute macroを提供していて、このマクロはユニットテストを非常に簡単にしてくれます。この attribute macroはテスト実行時に独立したテスト用の Databaseとそのコネクションを用意してくれて、テスト終了時には作成されたデータベースを削除します。 DB接続のために環境変数にDATABASE_URLを設定する必要があります。

詳細はドキュメント: https://docs.rs/sqlx/latest/sqlx/attr.test.htm

Support DB

Database

Requires DATABASE_URL

Postgres

Yes

MySQL

Yes

SQLite

No

アカウント struct と emailで探すfunctionの作成

説明ように Account struct と email で Account を探すための find_account_by_email を用意します。この find_account_by_email は 引数に emailDB pool を受け取ります。

pub struct Account {
    pub id: uuid::Uuid,
    pub email: String,
}

pub async fn find_account_by_email(
    pool: &sqlx::PgPool,
    email: &str,
) -> Result<Option<Account>, sqlx::Error> {
    let account = sqlx::query_as!(
        Account,
        r#"
            SELECT id, email
            FROM accounts
            WHERE email = $1
        "#,
        email
    )
    .fetch_optional(pool)
    .await?;

    Ok(account)
}

sqlx::test を使ってユニットテストをする

それではユニットテストを書いてみましょう。まず test_find_account_by_email を作成して #[sqlx::test()] をつけます。引数に pool: PgPool を入れてあげると sqlx::test が自動で作成した pool connection を自動で受け取るようになります。そのコネクションを使用してユニットテストを実行できます。

#[sqlx::test()]
async fn test_find_account_by_email(pool: PgPool) { // <-- add pool: PgPool
    let email = "test@test.com";
    let account = find_account_by_email(&pool, email)
        .await
        .expect("Failed to fetch account.")
        .expect("Account not found.");

    assert_eq!(account.email, email);
}

However, Database may NOT have test data, then you can use fixtures to add seeds before running test.
Let create /fixtures/accounts.sql for test data.

しかし、このままではテスト用のDBにデータがないため失敗するかと思います。その時は fixtures 機能を使うと seed をテスト実行時に入れることができます。/fixtures/accounts.sqlを作成します

-- /fixtures/accounts.sql

INSERT INTO accounts (id, email)
VALUES
  ('8489f87b-a6bc-4904-a893-601e4a8d074f'::uuid, 'test@church-navi.com');

Add #[sqlx::test(fixtures(path = "fixtures", scripts("accounts")))] then before running test, sqlx::test automatically run the accounts.sql to insert data. You can add many scripts in scripts("accounts", "orders", "etc")

次に#[sqlx::test(fixtures(path = "fixtures", scripts("accounts")))] を追加します。すると sqlx::test は自動的に作成された sql を読み取り実行してくれます。また複数のsqlを実行することも可能でその場合は scripts("accounts", "orders", "etc") のようにします。

NOTE: fixtures path RELATIVE PATH しか現在サポートしていないみたいです。

#[sqlx::test(fixtures(path = "fixtures", scripts("accounts")))]
async fn test_find_account_by_email(pool: PgPool) {
    let email = "test@test.com";
    let account = find_account_by_email(&pool, email)
        .await
        .expect("Failed to fetch account.")
        .expect("Account not found.");

    assert_eq!(account.email, email);
}