How to pass parameters during tests?

In the "good old days" when I was using languages without threads I used to use environment variables to pass parameters to tests. (e.g. the path to a temporary folder or the name of the database).

In each test-function I would generate a temporary name, put it in an environment variable. The application would check for the environment variable and use that if it was available.

Now that Rust runs the test functions in threads this broke down as environment variables are per-process and thus two test function running in two thread could end up using the same value of the environment variable. Not good.

One solution is to pass --test-threads=1 to cargo test, but I wonder if there was a better way to use temporary folders for tests?

Could you share a minimal code snippet of what your tests look like? I'm having a hard time to process why you'd have to pass arguments via the environment and not just as parameters to a function call. When I build some configurable application, I normally have a Config struct somewhere which I can fill from the environment (for production) or just by instantiating it programmatically (for tests).

This the smallest I managed to make it so far is here: Testing with tempfiles and environment variables. The real one I am currently working on is here.

It could be solved with a configuration file, but I'd still need to tell the application which configuration file to use in each test-case, right? Do you have a project where I might be able to look at your solution?

I see you are using rocket. While I'm not familiar with it, what I do in actix-web is have a Config struct that approximately looks like this (pseudo-code):

#[derive(Debug, Clone)]
struct Config {
    config_value_1: String,
    config_value_2: String,
}

impl Config {
    fn new(config_value_1: String, config_value_2) -> Self {
        Self {
            config_value_1,
            config_value_2,
        }
    }
    
    fn from_env() -> Self {
        Self::new(
            env!("config_value_1").unwrap(), 
            env!("config_value_2").unwrap(),
        }
    }
}

The config values are stuff like the URL of my database server, tokens, etc.

What I do normally is to create more useful types from that config, like database clients or other domain specific stuff which I attach to my server (with Rocket::manage in your case, I assume) so I can then use them in my endpoints (in actix-web via the web::Data extractor, which I believe has an equivalent in Rocket in State).

In my test, I'd instantiate Config using Config::new, in production I'd use Config::from_env. If I were you I'd wrap your Rocket instance you create here in a function that takes Config as an argument and then constructs the Rocket instance from that. You can then use that in your tests. Like here, for example:

#[test]
fn index_page() {
    let config = Config::new("some value 1".to_owned(), "some value 2".to_owned());
    let client = Client::tracked(super::rocket(config)).unwrap();
    ...
}
3 Likes

Thanks, I'll investigate this direction.

Hello, I was reading the source code of app_dirs2 and in their tests they use once_cell::sync::Lazy and a mutex to avoid overlapping of tests

https://github.com/app-dirs-rs/app_dirs2/blob/main/tests/fs.rs
It seems similar to what you want to do, I hope it is useful

1 Like