43648

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

Question:

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?

Answer1:

"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 <a href="http://doc.rust-lang.org/std/env/fn.set_var.html" rel="nofollow">the docs for set_var</a>, emphasis mine:

<blockquote>

Sets the environment variable k to the value v for the <strong>currently running process</strong>.

</blockquote>

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.

Answer2:

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: &'static str) -> Result<String, std::env::VarError>; } struct MockQuery; struct ActualQuery; impl QueryEnvironment for MockQuery { #[allow(unused_variables)] fn get_var(&self, var: &'static 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: &'static str) -> Result<String, std::env::VarError> { env::var(var) // Returns an actual response } } fn main() { 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 playpen: <a href="http://is.gd/QhUlDW" rel="nofollow">http://is.gd/QhUlDW</a>

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

Answer3:

A third option, and one I think is better, is to pass in the existing type - rather than creating a new abstraction that everyone would have to coerce to.

pub fn new<I>(vars: I) where I: Iterator<Item = (String, String)> { for (x, y) in vars { println!("{}: {}", x, y) } } #[test] fn trivial_call() { let vars = [("fred".to_string(), "jones".to_string())]; new(vars.iter().cloned()); }

Thanks to qrlpz on #rust for helping me get this sorted for my program, just sharing the result to help others :)

Recommend

  • How to convert a Set to an Array in Chrome?
  • Pytest/Allure - How to generate testcase description?
  • Paramiko SSHException Channel Closed
  • Doctrine2 inverse persistance not working in nested forms
  • Losing my session variables
  • Why can't UI components be accessed from a backgroundworker?
  • Why isn't my “Fizz Buzz” test in R working?
  • How to apply a custom handlers to only specific folder
  • how to get data attributes of dynamically generated element
  • Why people use prototype in javascript when it is easy to inherit using apply () and call () methods
  • sweetalert2 inputoptions from file in select example
  • In Java, how can I construct a File from a resource?
  • Can I use AllJoyn Framework for Wifi Direct in iOS?
  • Angular2 - Template reference inside NgSwitch
  • OSX - always hide certain files
  • Tell Git to stop prompting me for conflicts when none really exist?
  • Bigquery event streaming and table creation
  • Intel-64 and ia32 atomic operations acquire-release semantics and GCC 5+
  • Differences in dis-assembled C code of GCC and Borland?
  • Run multiple queries from 1 SQL file showing result in multiple tables
  • Query to find the duplicates between the name and number in table
  • Tamper-proof configuration files in .NET?
  • Unable to get column index with table.getColumn method using custom table Model
  • Parse a date string in a specific locale (not timezone!)
  • How to define and use opencv mat of user type
  • Django: Count of Group Elements
  • Handling un-mapped Rest path
  • What is the “return” in scheme?
  • recyclerView does not call the onBindViewHolder when scroll in the view
  • WinForms: two way TextBox problem
  • JSON with duplicate key names losing information when parsed
  • How to make Safari send if-modified-since header?
  • How to pass list parameters for each object using Spring MVC?
  • how does django model after text[] in postgresql [duplicate]
  • Setting background image for body element in xhtml (for different monitors and resolutions)
  • Bitwise OR returns boolean when one of operands is nil
  • sending mail using smtp is too slow
  • JaxB to read class hierarchy
  • costura.fody for a dll that references another dll
  • Binding checkboxes to object values in AngularJs