14

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
paul_h
  • 1,859
  • 3
  • 19
  • 27

1 Answers1

18

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:


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
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • what would you do if the `setup` function does some heavy initialization stuff like making a 3rd party service call or something, which you do not want to repeat for each test? – Tamil Vendhan Kanagarasu Aug 06 '22 at 02:05
  • @TamilVendhanKanagarasu that’s the lazy-static mentioned in the answer. See also https://stackoverflow.com/q/27791532/155423 – Shepmaster Aug 06 '22 at 02:09