Unit testing, mocking and traits in rust [duplicate] - unit-testing

This question already has answers here:
How to mock external dependencies in tests? [duplicate]
(1 answer)
How to mock specific methods but not all of them in Rust?
(2 answers)
How can I test stdin and stdout?
(1 answer)
Is there a way of detecting whether code is being called from tests in Rust?
(1 answer)
What is the proper way to use the `cfg!` macro to choose between multiple implementations?
(1 answer)
Closed 2 years ago.
Iam current building a application which heavy relies on File IO, so obviously lots of parts of my code have File::open(file).
Doing some integration tests are ok, I can easily set folders to load file and scenarios needed for it.
The problem comes whatever I want to unit tests, and code branches. I know there is lots of mocking libraries out there that claim to mocks, but i feel my biggest problem is code design itself.
Let's say for instance, I would do the same code in any object oriented language (java in the example), i could write some interfaces, and on tests simple override the default behavior I want to mock, set the a fake ClientRepository, whatever reimplemented wih a fixed return, or use some mocking framework, like mockito.
public interface ClientRepository {
Client getClient(int id)
}
public class ClientRepositoryDB {
private ClientRepository repository;
//getters and setters
public Client getClientById(int id) {
Client client = repository.getClient(id);
//Some data manipulation and validation
}
}
But i couldn`t manage to get the same results in rust, since we endup mixing data with behavior.
On the RefCell documentation, there is a similar example with the one I gave on java. Some of answers points to traits, clojures, conditional compiliation
We might come with some scenarios in test, first one a public function in some mod.rs
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SomeData {
pub name: Option<String>,
pub address: Option<String>,
}
pub fn get_some_data(file_path: PathBuf) -> Option<SomeData> {
let mut contents = String::new();
match File::open(file_path) {
Ok(mut file) => {
match file.read_to_string(&mut contents) {
Ok(result) => result,
Err(_err) => panic!(
panic!("Problem reading file")
),
};
}
Err(err) => panic!("File not find"),
}
// using serde for operate on data output
let some_data: SomeData = match serde_json::from_str(&contents) {
Ok(some_data) => some_data,
Err(err) => panic!(
"An error occour when parsing: {:?}",
err
),
};
//we might do some checks or whatever here
Some(some_data) or None
}
mod test {
use super::*;
#[test]
fn test_if_scenario_a_happen() -> std::io::Result<()> {
//tied with File::open
let some_data = get_some_data(PathBuf::new);
assert!(result.is_some());
Ok(())
}
#[test]
fn test_if_scenario_b_happen() -> std::io::Result<()> {
//We might need to write two files, and we want to test is the logic, not the file loading itself
let some_data = get_some_data(PathBuf::new);
assert!(result.is_none());
Ok(())
}
}
The second the same function becoming a trait and some struct implement it.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SomeData {
pub name: Option<String>,
pub address: Option<String>,
}
trait GetSomeData {
fn get_some_data(&self, file_path: PathBuf) -> Option<SomeData>;
}
pub struct SomeDataService {}
impl GetSomeData for SomeDataService {
fn get_some_data(&self, file_path: PathBuf) -> Option<SomeData> {
let mut contents = String::new();
match File::open(file_path) {
Ok(mut file) => {
match file.read_to_string(&mut contents) {
Ok(result) => result,
Err(_err) => panic!("Problem reading file"),
};
}
Err(err) => panic!("File not find"),
}
// using serde for operate on data output
let some_data: SomeData = match serde_json::from_str(&contents) {
Ok(some_data) => some_data,
Err(err) => panic!("An error occour when parsing: {:?}", err),
};
//we might do some checks or whatever here
Some(some_data) or None
}
}
impl SomeDataService {
pub fn do_something_with_data(&self) -> Option<SomeData> {
self.get_some_data(PathBuf::new())
}
}
mod test {
use super::*;
#[test]
fn test_if_scenario_a_happen() -> std::io::Result<()> {
//tied with File::open
let service = SomeDataService{}
let some_data = service.do_something_with_data(PathBuf::new);
assert!(result.is_some());
Ok(())
}
}
On both examples, we have a hard time unit testing it, since we tied with File::open, and surely, this might be extend to any non-deterministic function, like time, db connection, etc.
How would you design this or any similar code to make easier to unit testing and better design?

How would you design this or any similar code to make easier to unit testing and better design?
One way is to make get_some_data() generic over the input stream. The std::io module defines a Read trait for all things you can read from, so it could look like this (untested):
use std::io::Read;
pub fn get_some_data(mut input: impl Read) -> Option<SomeData> {
let mut contents = String::new();
input.read_to_string(&mut contents).unwrap();
...
}
You'd call get_some_data() with the input, e.g. get_some_data(File::open(file_name).unwrap()) or get_some_data(&mut io::stdin::lock()), etc. When testing, you can prepare the input in a string and call it as get_some_data(io::Cursor::new(prepared_data)).
As for the trait example, I think you misunderstood how to apply the pattern to your code. You're supposed to use the trait to decouple getting the data from processing the data, sort of how you'd use an interface in Java. The get_some_data() function would receive an object known to implement the trait.
Code more similar to what you'd find in an OO language might choose to use a trait object:
trait ProvideData {
fn get_data(&self) -> String
}
struct FileData(PathBuf);
impl ProvideData for FileData {
fn get_data(&self) -> String {
std::fs::read(self.0).unwrap()
}
}
pub fn get_some_data(data_provider: &dyn ProvideData) -> Option<SomeData> {
let contents = data_provider.get_data();
...
}
// normal invocation:
// let some_data = get_some_data(&FileData("file name".into()));
In test you'd just create a different implementation of the trait - for example:
#[cfg(test)]
mod test {
struct StaticData(&'static str);
impl ProvideData for StaticData {
fn get_data(&self) -> String {
self.0.to_string()
}
}
#[test]
fn test_something() {
let some_data = get_some_data(StaticData("foo bar"));
assert!(...);
}
}

First of all, I would like to thank #user4815162342 for enlightenment of traits. Using his answer as base, i solve with my own solution for the problem.
First, I build as mention, traits to better design my code:
trait ProvideData {
fn get_data(&self) -> String
}
But I had some problems, since there were tons of bad design code, and lots code I had to mock before run the test, something like the below code.
pub fn some_function() -> Result<()> {
let some_data1 = some_non_deterministic_function(PathBuf::new())?;
let some_data2 = some_non_deterministic_function_2(some_data1);
match some_data2 {
Ok(ok) => Ok(()),
Err(err) => panic!("something went wrong"),
}
}
I would need to change almost all functions signatures to accept Fn, this would not only change most my code, but will actually make it hard to read, since most of it I was changing for testing purpose only.
pub fn some_function(func1: Box<dyn ProvideData>, func2: Box<dyn SomeOtherFunction>) -> Result<()> {
let some_data1 = func1(PathBuf::new())?;
let some_data2 = func2(some_data1);
match some_data2 {
Ok(ok) => Ok(()),
Err(err) => panic!("something went wrong"),
}
}
Reading a little more deep the rust documentation, I slight changed the implementation.
Change almost all my code to use traits and structs ( Lots of code were public functions )
trait ProvideData {
fn get_data(&self) -> String;
}
struct FileData(PathBuf);
impl ProvideData for FileData {
fn get_data(&self) -> String {
String::from(format!("Pretend there is something going on here with file {}", self.0.to_path_buf().display()))
}
}
Add a new functions for default implementation in the structs, and add constructor with default implementation using dynamic dispatch functions.
struct SomeData(Box<dyn ProvideData>);
impl SomeData {
pub fn new() -> SomeData {
let file_data = FileData(PathBuf::new());
SomeData {
0: Box::new(file_data)
}
}
pub fn get_some_data(&self) -> Option<String> {
let contents = self.0.get_data();
Some(contents)
}
}
Since the constructor is private, we prevent user from injecting code, and we can freely change the internal implementation for testing purpose, and the integration tests keep running smooth.
fn main() {
//When the user call this function, it would no know that there is multiple implementations for it.
let some_data = SomeData::new();
assert_eq!(Some(String::from("Pretend there is something going on here with file ")),some_data.get_some_data());
println!("HEY WE CHANGE THE INJECT WITHOUT USER INTERATION");
}
And finally, since we test inside the declaration scope, we might change the injection even if is private:
mod test {
use super::*;
struct MockProvider();
impl ProvideData for MockProvider {
fn get_data(&self) -> String {
String::from("Mocked data")
}
}
#[test]
fn test_internal_data() {
let some_data = SomeData(Box::from(MockProvider()));
assert_eq!(Some(String::from("Mocked data")), some_data.get_some_data())
}
#[test]
fn test_ne_internal_data() {
let some_data = SomeData(Box::from(MockProvider()));
assert_ne!(Some(String::from("Not the expected data")), some_data.get_some_data())
}
}
The result code can be seem in the rust playground, hope this help user to design their code.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=62348977502accfed55fa4600d149bcd

Related

How to stub an external crate (or ways around it) in rust

I'm trying to test a struct I have that looks something like this
struct CANProxy {
socket: CANSocket
// other stuff .......
}
impl CANProxy {
pub fn new(can_device: &str) -> Self {
let socket = CANSocket::open(can_device).unwrap();
// other stuff .......
Self { socket }
}
}
What I want to test is that the proper messages are being sent across the socket, but I don't want to actually initialize a new can device while running my tests. I wanted to make a dummy CANSocket (which is from the cansocket crate) that uses the same functions and whatnot.
I tried creating a trait and extending the socketcan::CANSocket but it is super tedious and very redundant. I've looked at the mockall crate but I'm not sure if this would help in this situation. Is there an elegant way to accomplish what I want?
trait CANInterface {
fn open(name: &str) -> Result<Self, SomeError>;
// ... all the functions that are a part of the socketcan::CANSocket
// which is a lot of repetition
}
///////////// Proxy code
struct<T: CANInterface> CANProxy<T> {
socket: T
// other stuff .......
}
impl<T: CANInterface> CANProxy<T> {
pub fn open(can_device: &str) -> Result<Self, SomeError> {
let socket = T::open(can_device).unwrap();
// other stuff .......
Ok(Self { socket })
}
}
////////////// Stubbed CANInterfaces
struct FakeCANSocket;
impl CANInterface for FakeCANSocket {
// ..... implementing the trait here
}
// extension trait over here
impl CANInterface for socketcan::CANSocket {
// this is a lot of repetition and is kind of silly
// because I'm just calling the same things
fn open(can_device: &str) -> Self {
CANSocket::open(can_device)
}
/// ..............
/// ..............
/// ..............
}
So, first of all, there are indeed mock-targeted helper tools and crates such as ::mockall to help with these patterns, but only when you already have a trait-based API. If you don't, that part can be quite tedious.
For what is worth, know that there are also other helper crates to help write that boiler-plate-y and redundantly-delegating trait impls such as your open -> open situation. One such example could be the ::delegate crate.
Mocking it with a test-target Cargo feature
With all that being said, my personal take for your very specific situation —the objective is to override a genuine impl with a mock one, but just for testing purposes—, would be to forgo the structured but heavyweight approach of generics & traits, and to instead embrace "duck-typed" APIs, much like it is often done when having implementations on different platforms. In other words, the following suggestion, conceptually, could be interpreted as your test environment being one such special "platform".
You'd then #[cfg(…)]-feature-gate the usage of the real impl, that is, the CANSocket type, in one case, and #[cfg(not(…))]-feature gate a mock definition of your own CANSocket type, provided you managed to copy / mock all of the genuine's type API that you may, yourself, be using.
Add a mock-socket Cargo feature to your project:
[features]
mock-socket = []
Remark: some of you may be thinking of using cfg(test) rather than cfg(feature = "…"), but that approach only works for unit (src/… files with #[cfg(test)] mod tests, cargo test --lib invocation) tests, it doesn't for integration tests (tests/….rs files, cargo test --tests invocation) or doctests (cargo test --doc invocation), since the library itself is then compiled without cfg(test).
Then you can feature-gate Rust code using it
#[cfg(not(feature = "mock-socket"))]
use …path::to::genuine::CANSocket;
#[cfg(feature("mock-socket"))]
use my_own_mock_socket::CANSocket;
So that you can then define that my_own_mock_socket module (e.g., in a my_own_mock_socket.rs file using mod my_own_mock_socket; declaration), provided you don't forget to feature-gate it itself, so that the compiler doesn't waste time and effort compiling it when not using the mocked CANSocket (which would yield dead_code warnings and so on):
#[cfg(feature = "mock-socket")]
mod my_own_mock_socket {
//! It is important that you mimic the names and APIs of the genuine type!
pub struct CANSocket…
impl CANSocket { // <- no traits!
pub fn open(can_device: &str) -> Result<Self, SomeError> {
/* your mock logic */
}
…
}
}
That way, you can use:
either cargo test
or cargo test --features mock-socket
to run pick the implementation of your choice when running your tests
(Optional) if you know you will never want to run the tests for the real implementation, and only the mock one, then you may want to have that feature be enabled by default when running tests. While there is no direct way to achieve this, there is a creative way to work around it, by explicitly telling of the self-as-a-lib dev-dependency that test code has (this dependency is always present implicitly, for what is worth). By making it explicit, we can then use the classic features .toml attribute to enable features for that dev-dependency:
[dev-dependencies]
your_crate_name = { path = ".", features = ["mock-socket"] }
Bonus: not having to define an extra module for the mock code.
When the mock impls in question are short enough, it could be more tempting to just inline its definition and impl blocks. The issue then is that for every item so defined, it has to carry that #[cfg…] attribute which is annoying. That's when helper macros such as that of https://docs.rs/cfg-if can be useful, albeit adding a dependency for such a simple macro may seem a bit overkill (and, very personally, I find cfg_if!'s syntax too sigil heavy).
You can, instead, reimplement it yourself in less than a dozen lines of code:
macro_rules! cfg_match {
( _ => { $($tt:tt)* } $(,)? ) => ( $($tt)* );
( $cfg:meta => $expansion:tt $(, $($($rest:tt)+)?)? ) => (
#[cfg($cfg)]
cfg_match! { _ => $expansion }
$($(
#[cfg(not($cfg))]
cfg_match! { $($rest)+ }
)?)?
);
} use cfg_match;
With it, you can rewrite steps 2. and 3. above as:
cfg_match! {
feature = "mock-socket" => {
/// Mock implementation
struct CANSocket …
impl CANSocket { // <- no traits!
pub fn open(can_device: &str) -> Result<Self, SomeError> {
/* your mock logic */
}
…
}
},
_ => {
use …path::to::genuine::CANSocket;
},
}
You can avoid a lot of the boilerplate by using a macro to create the wrapper trait and implement it for the base struct. Simplified example:
macro_rules! make_wrapper {
($s:ty : $t:ident { $(fn $f:ident ($($p:ident $(: $pt:ty)?),*) -> $r:ty;)* }) => {
trait $t {
$(fn $f ($($p $(: $pt)?),*) -> $r;)*
}
impl $t for $s {
$(fn $f ($($p $(: $pt)?),*) -> $r { <$s>::$f ($($p),*) })*
}
}
}
struct TestStruct {}
impl TestStruct {
fn foo (self) {}
}
make_wrapper!{
TestStruct: TestTrait {
fn foo (self) -> ();
}
}
Playground
This will need to be extended to handle references (at least &self arguments), but you get the idea. You can refer to The Little Book of Rust Macros for more information on writing the macro.
Then you can use a crate like mockall to create your mock implementation of TestTrait or roll your own.

How to mock `std::fs::File` so can check if `File::set_len` was used correctly in unit tests?

As I check File::set_len(..) looks like it's implemented for struct File , but not via Trait.
Goal: test foo that takes file open as read/write , performs operations of : reads, writes, seeks, and trimming file to certain size. We like to provide initial state of file in test, and check result. Preferably in-memory.
How to test code that relies on set_len? (io::Seek or other traits didn't help so far).
I would like to mock it.
Let's make a toy example, to make discussion easier:
#![allow(unused_variables)]
use std::error::Error;
use std::fs::File;
use std::io::Cursor;
// assumes that file is open in Read/Write mode
// foo performs reads and writes and Seeks
// at the end wants to trim size of file to certain size.
fn foo(f: &mut File) -> Result<(), Box<dyn Error>> {
f.set_len(0)?;
Ok(())
}
fn main () -> Result<(), Box<dyn Error>> {
let mut buf = Vec::new();
let mut mockfile = Cursor::new(&buf);
// we would like to supply foo
// with "test" representation of initial file state
foo(&mut mockfile)
// and check afterwards if resulting contents (=> size)
// of file match expectations
}
on rust-play : https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=950a94504168d51f043966288fae3bca
Error:
error[E0308]: mismatched types
--> src/main.rs:15:9
|
15 | foo(&mut mockfile)
| ^^^^^^^^^^^^^ expected struct `File`, found struct `std::io::Cursor`
P.S. before receiving answers I started giving a shot to tempfile crate: https://docs.rs/tempfile/3.1.0/tempfile/#structs . Still, ideal solution is "in-memory" so can't wait for answers to question :).
In short, you can't mock std::fs::File given a function that requires that exact type - it's just not how Rust works.
However, if you have control over foo, you can easily invent a trait that has set_len and make foo generic over that trait. Since it's your trait, you can implement it for types defined elsewhere (such as File), which will make foo() accept File as it did before. But it will also accept anything else that implements the trait, including the mock types you create in the test suite. And thanks to monomorphization, its execution will be just as efficient as the original code. For example:
pub trait SetLen {
fn set_len(&mut self, len: u64) -> io::Result<()>;
}
impl SetLen for File {
fn set_len(&mut self, len: u64) -> io::Result<()> {
File::set_len(self, len)
}
}
pub fn foo(f: &mut impl SetLen) -> Result<(), Box<dyn Error>> {
f.set_len(0)?;
Ok(())
}
// You can always pass a `File` to `foo()`:
fn main() -> Result<(), Box<dyn Error>> {
let mut f = File::create("bla")?;
foo(&mut f)?;
Ok(())
}
To mock it, you would just define a type that implements the trait and records whether it's been called:
#[derive(Debug, Default)]
struct MockFile {
set_len_called: Option<u64>,
}
impl SetLen for MockFile {
fn set_len(&mut self, len: u64) -> io::Result<()> {
self.set_len_called = Some(len);
Ok(())
}
}
#[test]
fn test_set_len_called() {
let mut mf = MockFile::default();
foo(&mut mf).unwrap();
assert_eq!(mf.set_len_called, Some(0));
}
Playground

How to unit-test a deserialization function used in serde(deserialize_with)?

I have a struct which implements Deserialize and uses the serde(deserialize_with) on a field:
#[derive(Debug, Deserialize)]
struct Record {
name: String,
#[serde(deserialize_with = "deserialize_numeric_bool")]
is_active: bool,
}
The implementation of deserialize_numeric_bool deserializes a string "0" or "1" to the corresponding boolean value:
pub fn deserialize_numeric_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
where D: Deserializer<'de>
{
struct NumericBoolVisitor;
impl<'de> Visitor<'de> for NumericBoolVisitor {
type Value = bool;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("either 0 or 1")
}
fn visit_u64<E>(self, value: u64) -> Result<bool, E>
where E: DeserializeError
{
match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(E::custom(format!("invalid bool: {}", value))),
}
}
}
deserializer.deserialize_u64(NumericBoolVisitor)
}
(I appreciate comments about code improvements)
I'd like to write unit tests for deserialization functions like deserialize_numeric_bool. Of course, my friendly search box revealed the serde_test crate and a documentation page about unit-testing.
But these resources couldn't help me in my case, as the crate tests a structure directly implementing Deserialize.
One idea I had was to create a newtype which only contains the output of my deserialize functions and test it with it. But this looks like a unnecessary indirection to me.
#[derive(Deserialize)]
NumericBool {
#[serde(deserialize_with = "deserialize_numeric_bool")]
value: bool
};
How do I write idiomatic tests for it?
My current solution uses only structures already provided by serde.
In my use case, I only wanted to test that a given string will deserialize successfully into a bool or has a certain error. The serde::de::value provides simple deserializers for fundamental data types, for example U64Deserializer which holds a u64. It also has an Error struct which provides a minimal representation for the Error traits – ready to be used for mocking errors.
My tests look currently like that: I mock the input with a deserializer and pass it to my function under test. I like that I don't need an indirection there and that I have no additional dependencies. It is not as nice as the assert_tokens* provided serde_test, as it needs the error struct and feels less polished. But for my case, where only a single value is deserialized, it fulfills my needs.
use serde::de::IntoDeserializer;
use serde::de::value::{U64Deserializer, StrDeserializer, Error as ValueError};
#[test]
fn test_numeric_true() {
let deserializer: U64Deserializer<ValueError> = 1u64.into_deserializer();
assert_eq!(numeric_bool(deserializer), Ok(true));
}
#[test]
fn test_numeric_false() {
let deserializer: U64Deserializer<ValueError> = 0u64.into_deserializer();
assert_eq!(numeric_bool(deserializer), Ok(false));
}
#[test]
fn test_numeric_invalid_number() {
let deserializer: U64Deserializer<ValueError> = 2u64.into_deserializer();
let error = numeric_bool(deserializer).unwrap_err();
assert_eq!(error.description(), "invalid bool: 2");
}
#[test]
fn test_numeric_empty() {
let deserializer: StrDeserializer<ValueError> = "".into_deserializer();
let error = numeric_bool(deserializer).unwrap_err();
assert_eq!(error.description(), "invalid type: string \"\", expected either 0 or 1");
}
I hope that it helps other folks too or inspire other people to find a more polished version.
I've come across this question several times while trying to solve a similar problem recently. For future readers, pixunil's answer is nice, straightforward, and works well. However, I'd like to provide a solution using serde_test as the unit testing documentation mentions.
I researched how serde_test is used across a few crates that I found via its reverse dependencies on lib.rs. Several of them define small structs or enums for testing deserialization or serialization as you mentioned in your original post. I suppose doing so is idiomatic when testing would be too verbose otherwise.
Here's a few examples; this is a non-exhaustive list:
Example from time
Another example from time
Example from slab (tokio)
Example from bitcoin_hashes
Example from uuid
Example from euclid
Anyway, let's say I have a function to deserialize a bool from a u8 and another function that serializes a bool to a u8.
use serde::{
de::{Error as DeError, Unexpected},
Deserialize, Deserializer, Serialize, Serializer,
};
fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
match u8::deserialize(deserializer)? {
0 => Ok(false),
1 => Ok(true),
wrong => Err(DeError::invalid_value(
Unexpected::Unsigned(wrong.into()),
&"zero or one",
)),
}
}
#[inline]
fn bool_to_int<S>(a_bool: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if *a_bool {
serializer.serialize_u8(1)
} else {
serializer.serialize_u8(0)
}
}
I can test those functions by defining a struct in my test module. This allows constraining the tests to those functions specifically instead of ser/deserializing a larger object.
#[cfg(test)]
mod tests {
use super::{bool_from_int, bool_to_int};
use serde::{Deserialize, Serialize};
use serde_test::{assert_de_tokens_error, assert_tokens, Token};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[serde(transparent)]
struct BoolTest {
#[serde(deserialize_with = "bool_from_int", serialize_with = "bool_to_int")]
a_bool: bool,
}
const TEST_TRUE: BoolTest = BoolTest { a_bool: true };
const TEST_FALSE: BoolTest = BoolTest { a_bool: false };
#[test]
fn test_true() {
assert_tokens(&TEST_TRUE, &[Token::U8(1)])
}
#[test]
fn test_false() {
assert_tokens(&TEST_FALSE, &[Token::U8(0)])
}
#[test]
fn test_de_error() {
assert_de_tokens_error::<BoolTest>(
&[Token::U8(14)],
"invalid value: integer `14`, expected zero or one",
)
}
}
BoolTest is within the tests module which is gated by #[cfg(test)] as per usual. This means that BoolTest is only compiled for tests rather than adding bloat. I'm not a Rust expert, but I think this is a good alternative if a programmer wishes to use serde_test as a harness.

Creating an `std::env::Args` iterator for testing

Is there a way in Rust to create a std::env::Args from a Vec<String> in order to use it in a #[test] function?
I wish to test a function that gets a std::env::Args as an argument, but I don't know how to create such an object with a list of arguments I supply for the test.
I wasn't able to figure this one out from the docs, the source nor from Google searches.
The fields of std::env::Args are not documented, and there doesn't appear to be a public function to create one with custom fields. So, you're outta luck there.
But since it's just "An iterator over the arguments of a process, yielding a String value for each argument" your functions can take a String iterator or Vec without any loss of functionality or type safety. Since it's just a list of Strings, it doesn't make much sense to arbitrarily limit your functions to strings which happen to come from the command line.
Looking through Rust's own tests, that's just what they do. There's a lot of let args: Vec<String> = env::args().collect();
There's even an example in rustbuild where they strip off the name of the program and just feed the list of arguments.
use std::env;
use bootstrap::{Config, Build};
fn main() {
let args = env::args().skip(1).collect::<Vec<_>>();
let config = Config::parse(&args);
Build::new(config).build();
}
And bootstrap::Config::parse() looks like so:
impl Config {
pub fn parse(args: &[String]) -> Config {
let flags = Flags::parse(&args);
...
I'm not a Rust expert, but that seems to be how the Rust folks handle the problem.
#Schwern's answer is good and it led me to this simpler version. Since std::env::Args implements Iterator with Item = String you can do this:
use std::env;
fn parse<T>(args: T)
where
T: Iterator<Item = String>,
{
for arg in args {
// arg: String
print!("{}", arg);
}
}
fn main() {
parse(env::args());
}
To test, you provide parse with an iterator over String:
#[test]
fn test_parse() {
let args = ["arg1", "arg2"].iter().map(|s| s.to_string());
parse(args);
}
I've wrote a little macro to make this easier, based on #Rossman's answer (and therefore also based on #Schwern's answer; thanks go to both):
macro_rules! make_string_iter {
($($element: expr), *) => {
{
let mut v = Vec::new();
$( v.push(String::from($element)); )*
v.into_iter()
}
};
}
It can be used in that way:
macro_rules! make_string_iter {
($($element: expr), *) => {
{
let mut v = Vec::new();
$( v.push(String::from($element)); )*
v.into_iter()
}
};
}
// We're using this function to test our macro
fn print_args<T: Iterator<Item = String>>(args: T) {
for item in args {
println!("{}", item);
}
}
fn main() {
// Prints a, b and c
print_args(make_string_iter!("a", "b", "c"))
}
Or try it out on the Rust Playground.
I'm not (yet) an expert in rust, any suggestions are highly welcome :)

How to make a variable with a scope/lifecycle for all test functions in a Rust test?

I have a test that initializes a variable before diving into the detail of the test, and I want to make a second test with the same variable, and not duplicate the initialization code:
#[test]
fn test_one() {
let root = Path::new("data/");
// the rest of the test
}
#[test]
fn test_two() {
let root = Path::new("data/");
// the rest of the test
}
I don't think static or const would do it because the size would not be known up front, though PathBuf.from(path) might make that OK, except that initialization expressions for static/const vars cannot be too complex.
I've seen lazy_static, but have not seen any examples of its use in tests. This after seeing the compiler error with "an extern crate loading macros must be at the crate root", which online searching tells me is something about being outside main(), but tests don't have main functions.
In Java, I would define the variable then initialize it in a setup() method, but I can't see examples of that online for Rust.
Foremost, remember that Rust tests are run in parallel. This means that any shared setup needs to be thread-safe.
and not duplicate the initialization code
You do it the same way you avoid duplicating any other code: create a function, create a type, create traits, etc.:
use std::path::PathBuf;
fn root() -> PathBuf {
PathBuf::from("data/")
}
#[test]
fn test_one() {
let root = root();
// the rest of the test
}
#[test]
fn test_two() {
let root = root();
// the rest of the test
}
In Java I would define the variable, then initialize it in a setup() method
Instead, make a struct called Setup containing all those variables and construct it as the first thing in each test:
use std::path::{Path, PathBuf};
struct Setup {
root: PathBuf,
}
impl Setup {
fn new() -> Self {
Self {
root: PathBuf::from("data/"),
}
}
}
#[test]
fn test_one() {
let setup = Setup::new();
let root: &Path = &setup.root;
// the rest of the test
}
#[test]
fn test_two() {
let setup = Setup::new();
let root: &Path = &setup.root;
// the rest of the test
}
but have not seen any examples of [lazy-static] use in tests
That's because there is no different way to use it in tests, it's just code:
use lazy_static::lazy_static; // 1.4.0
use std::path::Path;
lazy_static! {
static ref ROOT: &'static Path = Path::new("data/");
}
#[test]
fn test_one() {
let root = *ROOT;
// the rest of the test
}
#[test]
fn test_two() {
let root = *ROOT;
// the rest of the test
}
See also:
How to initialize the logger for integration tests?
How do I create a global, mutable singleton?
Very specifically for your case, it's very rare that you need exactly a Path, since a string slice implements AsRef<Path>. Said another way, most places that accept a Path accept a &str:
static ROOT: &str = "data/";
#[test]
fn test_one() {
let root = ROOT;
// the rest of the test
}
#[test]
fn test_two() {
let root = ROOT;
// the rest of the test
}