I am encountering an issue with writing integration tests in Rust for an application that uses a database (PostgreSQL managed via SeaORM).
Some of my tests need to use the database to be properly executed. Additionally, all my tests run in parallel, so if a test requires the database, a temporary database is created specifically for that test. In other words, a temporary database is created per test.
Here are simplified examples of tests that require database access:
#[test]
async fn test_foo() {
// Create a temporary database only for the test test_foo()
let db_connection_uri = get_db_connection_uri(); // returns "postgres://dbuser:[email protected]:5432" defined in the .env
let app_db_name = get_app_db_name(); // returns the database name (db_name) used by the application
let temp_db_name = get_temp_db_name(); // returns "test_db_" + random UUID
let db_connection = common::database::setup(&db_connection_uri, &app_db_name, &temp_db_name).await; // creates a temporary database and returns a sea_orm::database::db_connection::DatabaseConnection
// Add test data to the temporary database
// ...
foo_entity.insert(&db_connection).await?;
// Core of the test
do_something();
// Delete the temporary database at the end of the test
common::database::cleanup(&db_connection_uri, &app_db_name, &temp_db_name).await;
}
#[test]
async fn test_faa() {
// Create a temporary database only for the test test_faa()
// ...
// Add test data to the temporary database
// ...
// Core of the test
do_something2();
// Delete the temporary database at the end of the test
// ...
}
#[test]
async fn test_fuu() {
// ...
}
// ...
The functions do_something(), do_something2(), ... need to access a database.
The Issue
Inside my functions do_something(), do_something2(), ..., I need to access the database. These functions rely on the .env file configuration by default to connect to the main application database ("postgres://dbuser:[email protected]:5432/dbname").
However, in the context of integration tests, a temporary database is created at the beginning of the test (for the tests that require it) to execute the test.
A possible solution would be to pass &db_connection as a parameter when calling do_somethingX() (like do_something(&db_connection);).
The problem with this approach is that I would need to modify all functions that interact with the database to add a parameter to pass &db_connection, which is not ideal.
What I Am Looking For
I'm looking for an elegant solution to automatically adapt &db_connection based on whether the function is executed in a test (in which case, &db_connection should point to a temporary test database) or outside a test (where it should point to the actual application database).
I would like to avoid systematically adding the same piece of code in each function to adapt &db_connection based on the context.
For example, I do not want to have to use an approach like this:
fn do_something1_with_db(db_connection: Option<&DatabaseConnection>, param2: ..., param3: ...) {
let db_con = db_connection.unwrap_or(get_db_connection_using_env_config());
// ...
}
fn do_something2_with_db(db_connection: Option<&DatabaseConnection>) {
let db_con = db_connection.unwrap_or(get_db_connection_using_env_config());
// ...
}
// ...
fn do_somethingN_with_db(db_connection: Option<&DatabaseConnection>, param2: ...) {
let db_con = db_connection.unwrap_or(get_db_connection_using_env_config());
// ...
}
This approach adds unnecessary complexity to each function, requiring extra logic to determine the correct db_connection (test or non-test) and adding an additional parameter to all relevant functions.
I am looking for a cleaner and more maintainable solution to handle this automatically.
do_somethingNaccess the database of you don't pass it in? Are you using a global? Are you establishing a new connection each time? Both are pretty bad form.ConnectOptions::new("...")where ... is from my .env file ). This is done in theget_db_connection_using_env_config()functiontest_*before you calldo_somethingNthat will override the values from.env, but 3. Tests are run in parallel, so modifying the environment in one test may affect the other tests randomly depending on which test wrote the environment last.