0

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.

6
  • 4
    Wait a minute, how does do_somethingN access 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. Commented Mar 7 at 1:21
  • in do_somethingN, I use something like this (ConnectOptions::new("...") where ... is from my .env file ). This is done in the get_db_connection_using_env_config() function Commented Mar 7 at 10:24
  • 1. That's a bad idea. 2. You can set environment variables in test_* before you call do_somethingN that 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. Commented Mar 7 at 13:15
  • to be sure to understand, what is a bad idea ? for 2, indeed, its not a good approach for // tests ! Commented Mar 7 at 14:32
  • after some searches, best solutions seems finally to pass th db_connection through the function signature or share the db_connection with a thread_local (the thread_local belongs the test, and I can store in the thread_local the db_connection) Commented Mar 7 at 14:35

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.