virtual-io 0.1.0

Mock stdin/out/err for testing
Documentation
use crate::helpers::{append_stdout, VioFakeMessage};
use crate::vio::VirtualIo;
use std::env::VarError;

pub struct VioFake {
    pub(crate) environment_variables: Vec<(String, String)>,
    pub(crate) expected_messages: Vec<VioFakeMessage>,
    actual_messages: Vec<VioFakeMessage>,
    messages_index: usize,
}

impl VioFake {
    pub(crate) fn new(
        expected_messages: Vec<VioFakeMessage>,
        environment_variables: Vec<(String, String)>,
    ) -> Self {
        Self {
            expected_messages,
            environment_variables,
            actual_messages: Vec::new(),
            messages_index: 0,
        }
    }

    #[must_use]
    pub const fn get_expected(&self) -> &Vec<VioFakeMessage> {
        &self.expected_messages
    }

    #[must_use]
    pub const fn get_actual(&self) -> &Vec<VioFakeMessage> {
        &self.actual_messages
    }
}

impl VirtualIo for VioFake {
    fn print<S: Into<String>>(&mut self, message: S) -> &mut Self {
        let new_message_added = append_stdout(&mut self.actual_messages, message.into());
        if new_message_added {
            self.messages_index += 1;
        }
        self
    }

    fn println<S: Into<String>>(&mut self, message: S) -> &mut Self {
        let new_message_added = append_stdout(&mut self.actual_messages, message.into());
        append_stdout(&mut self.actual_messages, "\n".to_string());
        if new_message_added {
            self.messages_index += 1;
        }
        self
    }

    fn read_line(&mut self) -> String {
        match self.expected_messages.get(self.messages_index) {
            Some(VioFakeMessage::StdIn(ref message)) => {
                self.messages_index += 1;
                self.actual_messages
                    .push(VioFakeMessage::StdIn(message.clone()));
                message.clone()
            }
            _ => panic!(
                "Expected STDIN message, instead got {:?}",
                self.expected_messages[self.messages_index]
            ),
        }
    }

    fn get_environment_var<S: Into<String>>(&self, variable: S) -> Result<String, VarError> {
        let name = variable.into();
        for var in &self.environment_variables {
            if var.0 == name {
                return Ok(var.1.clone());
            }
        }
        Err(VarError::NotPresent)
    }

    fn get_environment_vars(&self) -> Vec<(String, String)> {
        self.environment_variables.clone()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::vio::VirtualIo;
    use crate::VioFakeBuilder;

    #[test]
    fn vio_fake_handles_correct_print_messages() {
        let mut vio = VioFakeBuilder::new()
            .expect_stdout("My name is ")
            .expect_stdout("John Smith\n")
            .expect_stdout("Your name is Jane Doe\n")
            .expect_stdout("Together, ")
            .expect_stdout("we are two people\n")
            .build();
        vio.println("My name is John Smith");
        vio.println("Your name is Jane Doe");
        vio.println("Together, we are two people");
        assert_eq!(vio.get_expected(), vio.get_actual());
    }

    #[test]
    fn vio_fake_handles_incorrect_print_messages() {
        let mut vio = VioFakeBuilder::new().expect_stdout("Hello, world!").build();
        vio.println("Hello, Jane Doe!");
        assert_ne!(vio.get_expected(), vio.get_actual());
    }

    #[test]
    fn vio_fake_handles_correct_read_line_messages() {
        let mut vio = VioFakeBuilder::new()
            .provide_stdin("My name is Jane Doe")
            .build();
        let output = vio.read_line();
        assert_eq!(output, "My name is Jane Doe");
        assert_eq!(vio.get_expected(), vio.get_actual());
    }

    #[test]
    fn vio_fake_handles_correct_ordering_of_io() {
        let mut vio = VioFakeBuilder::new()
            .expect_stdout("My name is ")
            .expect_stdout("John Smith\n")
            .provide_stdin("My name is Jane Doe")
            .expect_stdout("Your name is Jane Doe\n")
            .expect_stdout("Together, ")
            .expect_stdout("we are two people\n")
            .build();
        vio.println("My name is John Smith");
        let output = vio.read_line();
        assert_eq!(output, "My name is Jane Doe");
        vio.println("Your name is Jane Doe");
        vio.println("Together, we are two people");
        assert_eq!(vio.get_expected(), vio.get_actual());
    }

    #[test]
    #[should_panic]
    fn vio_fake_panics_on_incorrect_ordering_of_io() {
        let mut vio = VioFakeBuilder::new()
            .provide_stdin("My name is Jane Doe")
            .build();
        vio.println("My name is John Smith");
        vio.read_line();
    }

    #[test]
    #[should_panic]
    fn vio_fake_panics_if_not_enough_inputs() {
        let mut vio = VioFakeBuilder::new()
            .provide_stdin("My name is Jane Doe")
            .build();
        vio.println("My name is John Smith");
        vio.read_line();
        vio.read_line();
    }

    #[test]
    fn vio_get_environment_variable() {
        let vio = VioFakeBuilder::new()
            .set_environment_var("HELLO", "WORLD")
            .build();
        assert_eq!(vio.get_environment_var("HELLO"), Ok("WORLD".to_string()));
        assert_eq!(
            vio.get_environment_var("DOES_NOT_EXIST"),
            Err(VarError::NotPresent)
        );
    }

    #[test]
    fn vio_get_environment_variables() {
        let vio = VioFakeBuilder::new()
            .set_environment_var("HELLO", "WORLD")
            .set_environment_var("MY_MOOD", "GOOD")
            .build();
        assert_eq!(
            vio.get_environment_vars(),
            vec![
                ("HELLO".to_string(), "WORLD".to_string()),
                ("MY_MOOD".to_string(), "GOOD".to_string())
            ]
        );
    }
}