Reputation: 5820
I have created a Server
struct which consists out of 2 ports, an IP address and a port.
The value assigned to the port could NOT be specified, but it's read from the system's environment variables. When the environment variable has not been found, or when it's not a valid value, it defaults to a standard port.
struct Server {
ip: String,
port: u16,
}
impl Server {
const STD_PORT: u16 = 4333;
const PORT_ENV_VAR: &'static str = "MEMDB_PORT";
fn new(ip: String) -> Server {
Server {
ip: ip,
port: Server::get_port(),
}
}
fn get_port() -> u16 {
match env::var(Server::PORT_ENV_VAR) {
Ok(val) => match val.parse::<u16>() {
Ok(val) => val,
Err(_) => Server::STD_PORT,
},
Err(_) => Server::STD_PORT,
}
}
}
When I want to create tests for it, it can be done in the following way:
#[cfg(test)]
mod tests {
use crate::Server;
#[test]
fn test_create_server_assigns_correct_ip() {
// WHEN:
let server = crate::Server::new("127.0.0.1".to_owned());
// THEN:
assert_eq!(server.ip, "127.0.0.1");
}
#[test]
fn test_create_server_assigns_correct_port() {
// WHEN:
let server = crate::Server::new("".to_owned());
// THEN:
assert_eq!(server.port, 4333);
}
}
Off course, this test produces a different result when the environment variable MEMDB_PORT
has a value assigned.
How would I be able to verify that the assigned port can be controlled by setting the correct environment variable?
I was thinking about creating a trait that defined the API to access the system's environment variables.
trait EnvironmentReader {
fn get(key: &str) -> Option<String>;
}
struct OSEnvironmentReader {}
impl EnvironmentReader for OSEnvironmentReader {
fn get(key: &str) -> Option<String> {
return match env::var(key) {
Ok(val) => Some(val),
Err(_) => None,
};
}
}
Is this the correct way on how to do it in Rust? If so, how should I modify my struct to use this trait? Add it as a field to the struct itself, and how can I do this with the new 'dyn' stuff?
Upvotes: 2
Views: 938
Reputation: 181785
There is no need for dyn
or traits here. Simply create a configuration struct:
pub struct ServerConfig {
pub port: u16,
}
impl ServerConfig {
const STD_PORT: u16 = 4333;
const PORT_ENV_VAR: &'static str = "MEMDB_PORT";
fn from_environment() -> ServerConfig {
ServerConfig {
port: Self::port_from_env(),
}
}
fn port_from_env() -> u16 {
// Also shortened the `match` a bit here. Could make this generic too.
env::var(ServerConfig::PORT_ENV_VAR)
.map(|val| val.parse::<u16>())
.unwrap_or(ServerConfig::STD_PORT)
}
}
Then pass it into your Server
when constructing it:
struct Server {
config: Config,
ip: String,
}
impl Server {
fn new(config: Config, ip: String) -> Server {
Server {
config,
ip,
}
}
}
In production code, pass ServerConfig::from_environment()
. In your test code, pass a custom ServerConfig { port: 4333 }
. You're still not testing whether the port is correctly taken from the environment, but in the comments you mentioned that you didn't want that and would rather mock it out.
You could also move ip
into ServerConfig
if it fits better with your design.
Upvotes: 1