Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test Rust methods that depend on environment variables?

I am building a library that interrogates its running environment to return values to the asking program. Sometimes as simple as

pub fn func_name() -> Option<String> {
    match env::var("ENVIRONMENT_VARIABLE") {
        Ok(s) => Some(s),
        Err(e) => None
    }
}

but sometimes a good bit more complicated, or even having a result composed of various environment variables. How can I test that these methods are functioning as expected?

like image 964
IceyEC Avatar asked Mar 08 '16 03:03

IceyEC


2 Answers

"How do I test X" is almost always answered with "by controlling X". In this case, you need to control the environment variables:

use std::env;

fn env_is_set() -> bool {
    match env::var("ENVIRONMENT_VARIABLE") {
        Ok(s) => s == "yes",
        _ => false
    }
}

#[test]
fn when_set_yes() {
    env::set_var("ENVIRONMENT_VARIABLE", "yes");
    assert!(env_is_set());
}

#[test]
fn when_set_no() {
    env::set_var("ENVIRONMENT_VARIABLE", "no");
    assert!(!env_is_set());
}

#[test]
fn when_unset() {
    env::remove_var("ENVIRONMENT_VARIABLE");
    assert!(!env_is_set());
}

However, you need to be aware that environment variables are a shared resource. From the docs for set_var, emphasis mine:

Sets the environment variable k to the value v for the currently running process.

You may also need to be aware that the Rust test runner runs tests in parallel by default, so it's possible to have one test clobber another.

Additionally, you may wish to "reset" your environment variables to a known good state after the test.

like image 114
Shepmaster Avatar answered Oct 20 '22 23:10

Shepmaster


Your other option (if you don't want to mess around with actually setting environment variables) is to abstract the call away. I am only just learning Rust and so I am not sure if this is "the Rust way(tm)" to do it... but this is certainly how I would do it in another language/environment:

use std::env;

pub trait QueryEnvironment {
    fn get_var(&self, var: &str) -> Result<String, std::env::VarError>;
}

struct MockQuery;
struct ActualQuery;

impl QueryEnvironment for MockQuery {
    fn get_var(&self, _var: &str) -> Result<String, std::env::VarError> {
        Ok("Some Mocked Result".to_string()) // Returns a mocked response
    }
}

impl QueryEnvironment for ActualQuery {
    fn get_var(&self, var: &str) -> Result<String, std::env::VarError> {
        env::var(var) // Returns an actual response
    }
}

fn main() {
    env::set_var("ENVIRONMENT_VARIABLE", "user"); // Just to make program execute for ActualQuery type
    let mocked_query = MockQuery;
    let actual_query = ActualQuery;
    
    println!("The mocked environment value is: {}", func_name(mocked_query).unwrap());
    println!("The actual environment value is: {}", func_name(actual_query).unwrap());
}

pub fn func_name<T: QueryEnvironment>(query: T) -> Option<String> {
    match query.get_var("ENVIRONMENT_VARIABLE") {
        Ok(s) => Some(s),
        Err(_) => None
    }
}

Example on the rust playground

Notice how the actual call panics. This is the implementation you would use in actual code. For your tests, you would use the mocked ones.

like image 8
Simon Whitehead Avatar answered Oct 21 '22 00:10

Simon Whitehead