Rust sqlx::test がユニットテストに便利だった。テストDBを自動で用意。
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 |
---|---|
Postgres | Yes |
MySQL | Yes |
SQLite | No |
アカウント struct と emailで探すfunctionの作成
説明ように Account struct と email で Account を探すための find_account_by_email を用意します。この find_account_by_email は 引数に email
と DB 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);
}