55

I have a Rust app (a simple interpreter) that needs some setup (initialize a repo) before the environment is usable.

I understand that Rust runs its tests (via cargo test) in a multithreaded manner, so I need to initialize the repo before any tests run. I also need to do this only once per run, not before each test.

In Java's JUnit this would be done with a @BeforeClass (or @BeforeAll in JUnit 5) method. How can I acheive the same thing in Rust?

kittylyst
  • 5,640
  • 2
  • 23
  • 36

3 Answers3

51

There's nothing built-in that would do this but this should help (you will need to call initialize() in the beginning of every test):

use std::sync::Once;

static INIT: Once = Once::new();

pub fn initialize() {
    INIT.call_once(|| {
        // initialization code here
    });
}
Jussi Kukkonen
  • 13,857
  • 1
  • 37
  • 54
46

If you use the ctor crate, you can take advantage of a global constructor function that will run before any of your tests are run.

Here's an example initialising the popular env_logger crate (assuming you have added ctor to your [dev-dependencies] section in your Cargo.toml file):

#[cfg(test)]
#[ctor::ctor]
fn init() {
    env_logger::init();
}

The function name is unimportant and you may name it anything.

Brendan Molloy
  • 1,784
  • 14
  • 22
  • I should put this into each `.rs` file? – yegor256 Feb 07 '22 at 15:56
  • @yegor256 Every test file is a different executable program, so yes. But you could also create a [common module](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests) so that you only need to define it once – kelloti Dec 09 '22 at 01:06
25

Just to give people more ideas (for example, how not to call setup in every test), one additional thing you could do is to write a helper like this:

fn run_test<T>(test: T) -> ()
    where T: FnOnce() -> () + panic::UnwindSafe
{
    setup();    
    let result = panic::catch_unwind(|| {
        test()
    });    
    teardown();    
    assert!(result.is_ok())
}

Then, in your own tests you would use it like this:

#[test]
fn test() {
    run_test(|| {
        let ret_value = function_under_test();
        assert!(ret_value);
    })
}

You can read more about UnwindSafe trait and catch_unwind here: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

I've found the original idea of this test helper in this medium article by Eric Opines.

Also, there is rstest crate which has pytest-like fixtures which you can use as a setup code (combined with the Jussi Kukkonen's answer:

use std::sync::Once; 
use rstest::rstest;
static INIT: Once = Once::new();

pub fn setup() -> () { 
    INIT.call_once(|| {
        // initialization code here
    });
}

#[rstest]
fn should_success(setup: ()) {
    // do your test
}

Maybe one day rstest will gain scopes support and Once won't be needed anymore.

valignatev
  • 6,020
  • 8
  • 37
  • 61