I am writing a CLI question asking library for my first Rust project since I will probably be using it anyway, and I cannot find a clean way to test the terminal method of the builder pattern, which using the configuration gets user input and returns an answer.
pub fn confirm(&mut self) -> Answer {
self.yes_no();
self.build_prompt();
let prompt = self.prompt.clone();
let valid_responses = self.valid_responses.clone().unwrap();
loop {
let stdio = io::stdin();
let input = stdio.lock();
let output = io::stdout();
if let Ok(response) = prompt_user(input, output, &prompt) {
for key in valid_responses.keys() {
if *response.trim().to_lowercase() == *key {
return valid_responses.get(key).unwrap().clone();
}
}
self.build_clarification();
}
}
}
Looking for a solution I discovered dependency injection which allowed me to write tests for the function that prompts the user for input using Cursor. It does not let me change the user input to the confirm() function for each test of Question::new("Continue?").confirm() though so I tried using conditional compilation, and came up with the following.
#[cfg(not(test))]
fn prompt_user<R, W>(mut reader: R, mut writer: W, question: &str) -> Result<String, std::io::Error>
where
R: BufRead,
W: Write,
{
write!(&mut writer, "{}", question)?;
let mut s = String::new();
reader.read_line(&mut s)?;
Ok(s)
}
#[cfg(test)]
fn prompt_user<R, W>(mut reader: R, mut writer: W, question: &str) -> Result<String, std::io::Error>
where
R: BufRead,
W: Write,
{
use tests;
Ok(unsafe { tests::test_response.to_string() })
}
And in the tests module I use a global variable:
pub static mut test_response: &str = "";
#[test]
fn simple_confirm() {
unsafe { test_response = "y" };
let answer = Question::new("Continue?").confirm();
assert_eq!(Answer::YES, answer);
}
This works as long as I only run tests with a single thread, but also no longer allows me to test the real user input function. Not really a problem for such a small crate but it is very messy. I did not see any solutions to do this from any available testing libraries.