db_testkit/macros.rs
1use std::future::Future;
2
3#[allow(unused)]
4use crate::{
5 backend::{Connection, DatabaseBackend, DatabasePool},
6 backends::PostgresBackend,
7 env::get_postgres_url,
8 pool::PoolConfig,
9 template::DatabaseTemplate,
10};
11
12#[cfg(feature = "mysql")]
13use crate::{backends::mysql::MySqlBackend, env::get_mysql_url};
14
15#[cfg(feature = "sqlx-postgres")]
16use crate::{backends::sqlx::SqlxPostgresBackend, env::get_sqlx_postgres_url};
17
18#[cfg(feature = "sqlx-sqlite")]
19use crate::{backends::sqlite::SqliteBackend, env::get_sqlite_url};
20
21/// A test database instance that provides access to a connection pool and test-specific data.
22///
23/// This struct is generic over the database backend type and provides methods for setting up
24/// and interacting with a test database instance.
25///
26/// # Type Parameters
27///
28/// * `'a` - The lifetime of the database pool reference
29/// * `B` - The database backend type that implements [`DatabaseBackend`]
30pub struct TestDatabase<'a, B: DatabaseBackend> {
31 /// The connection pool for this test database
32 pub test_pool: &'a B::Pool,
33 /// A unique identifier for the test user, useful for test data isolation
34 pub test_user: String,
35}
36
37impl<'a, B: DatabaseBackend> TestDatabase<'a, B> {
38 /// Sets up the test database by executing the provided setup function.
39 ///
40 /// This method acquires a connection from the pool, executes the setup function with that
41 /// connection, and returns the result.
42 ///
43 /// # Type Parameters
44 ///
45 /// * `F` - The setup function type
46 /// * `Fut` - The future type returned by the setup function
47 /// * `T` - The result type of the setup function
48 ///
49 /// # Arguments
50 ///
51 /// * `f` - A function that takes a database connection and returns a future
52 ///
53 /// # Returns
54 ///
55 /// Returns a `Result` containing the value returned by the setup function, or an error
56 /// if the setup failed.
57 pub async fn setup<F, Fut, T>(&self, f: F) -> Result<T, Box<dyn std::error::Error>>
58 where
59 F: FnOnce(B::Connection) -> Fut,
60 Fut: Future<Output = Result<T, Box<dyn std::error::Error>>>,
61 {
62 let conn = self.test_pool.acquire().await?;
63 let result = f(conn).await?;
64 Ok(result)
65 }
66}
67
68/// Creates a new PostgreSQL test database and executes the provided test function.
69///
70/// This macro handles the creation of a temporary database, executes the test function,
71/// and ensures proper cleanup after the test completes.
72///
73/// # Arguments
74///
75/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
76///
77/// # Example
78///
79/// ```rust
80/// #[tokio::test]
81/// async fn test_users() {
82/// with_test_db(|db| async move {
83/// db.setup(|mut conn| async move {
84/// conn.execute("CREATE TABLE users (id SERIAL PRIMARY KEY)").await?;
85/// Ok(())
86/// }).await?;
87/// Ok(())
88/// }).await;
89/// }
90/// ```
91#[macro_export]
92macro_rules! with_test_db {
93 ($f:expr) => {{
94 let backend = PostgresBackend::new(&get_postgres_url().unwrap())
95 .await
96 .unwrap();
97 let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
98 .await
99 .unwrap();
100
101 let db = template.get_immutable_database().await.unwrap();
102 let test_db = TestDatabase {
103 test_pool: db.get_pool(),
104 test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
105 };
106
107 $f(test_db).await
108 }};
109}
110
111/// Creates a new MySQL test database and executes the provided test function.
112///
113/// Similar to [`with_test_db`], but uses MySQL as the backend.
114///
115/// # Arguments
116///
117/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
118#[cfg(feature = "mysql")]
119#[macro_export]
120macro_rules! with_mysql_test_db {
121 ($f:expr) => {{
122 let backend = MySqlBackend::new(&get_mysql_url().unwrap()).await.unwrap();
123 let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
124 .await
125 .unwrap();
126
127 let db = template.get_immutable_database().await.unwrap();
128 let test_db = TestDatabase {
129 test_pool: db.get_pool(),
130 test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
131 };
132
133 $f(test_db).await
134 }};
135}
136
137/// Creates a new SQLx PostgreSQL test database and executes the provided test function.
138///
139/// Similar to [`with_test_db`], but uses SQLx's PostgreSQL implementation as the backend.
140///
141/// # Arguments
142///
143/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
144#[cfg(feature = "sqlx-postgres")]
145#[macro_export]
146macro_rules! with_sqlx_test_db {
147 ($f:expr) => {{
148 let backend = SqlxPostgresBackend::new(&get_sqlx_postgres_url().unwrap())
149 .await
150 .unwrap();
151 let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
152 .await
153 .unwrap();
154
155 let db = template.get_immutable_database().await.unwrap();
156 let test_db = TestDatabase {
157 test_pool: db.get_pool(),
158 test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
159 };
160
161 $f(test_db).await
162 }};
163}
164
165/// Creates a new SQLite test database and executes the provided test function.
166///
167/// Similar to [`with_test_db`], but uses SQLite as the backend.
168///
169/// # Arguments
170///
171/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
172#[cfg(feature = "sqlx-sqlite")]
173#[macro_export]
174macro_rules! with_sqlite_test_db {
175 ($f:expr) => {{
176 let backend = SqliteBackend::new(&get_sqlite_url().unwrap())
177 .await
178 .unwrap();
179 let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
180 .await
181 .unwrap();
182
183 let db = template.get_immutable_database().await.unwrap();
184 let test_db = TestDatabase {
185 test_pool: db.get_pool(),
186 test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
187 };
188
189 $f(test_db).await
190 }};
191}