45

I have a crate with production code in the src directory and integration tests in the tests directory. The production code uses log macros.

I would like to init a global logger when running the integration tests (e.g. env_logger::init().unwrap();) There are several tests and the test order is not defined, so I don't know in which test I should put the initialize command.

Is there any way I can do this nicely? Perhaps by overriding the tests main function?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dmitry Uvarov
  • 673
  • 6
  • 6

4 Answers4

45

You can use something like this:

use std::sync::Once;

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

/// Setup function that is only run once, even if called multiple times.
fn setup() {
    INIT.call_once(|| {
        env_logger::init().unwrap();
    });
}

Then simply call setup() in the beginning of each test.

Originally based on this blogpost.

fx-kirin
  • 1,906
  • 1
  • 20
  • 33
Danilo Bargen
  • 18,626
  • 15
  • 91
  • 127
  • Could anyone provide a full example? Where this code should go? – xliiv Sep 21 '20 at 16:23
  • 1
    @xliiv Just copy this code in your test file, then call `setup()` in each test. Beware, the `env_logger::init()` no longer returns a `Result`, hence the `unwrap` should be removed (see `try_init` for a `Result`). – jferard Nov 12 '21 at 10:53
  • 1
    This technique also works to initialize a `simple_logger` instance by substituting `env_logger::init().unwrap()` with `simple_logger::init_with_env().unwrap()`. – Joseph Cottam Jul 06 '22 at 21:07
13

The latest documentation has a recommendation on Capturing logs in tests :: env_logger:

Records logged during cargo test will not be captured by the test harness by default. The Builder::is_test method can be used in unit tests to ensure logs will be captured:

#[cfg(test)]
mod tests {
    fn init() {
        let _ = env_logger::builder().is_test(true).try_init();
    }
 
    #[test]
    fn it_works() {
        init();
        info!("This record will be captured by `cargo test`");
 
        assert_eq!(3, 1 + 2);
    }
}
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Patrick Tescher
  • 3,387
  • 1
  • 18
  • 31
  • 3
    I don't know what the documentation is attempting to show, but the `init` function is never called by anything and the `info!` line is not printed when the test either passes or fails (via `RUST_LOG=info cargo test`). – Shepmaster Mar 20 '19 at 19:50
  • 1
    The `init` function has to be called explicitly, it's fixed in [docs for v0.6.2](https://docs.rs/env_logger/0.6.2/env_logger/#capturing-logs-in-tests). Also, log printing happens only if the test fails (which i think is good). – Jiří Stránský Aug 20 '19 at 15:10
  • I'm getting `panicked at 'env_logger::init should not be called after logger initialized: SetLoggerError(())',` – xliiv Sep 21 '20 at 16:26
  • This looked promising but had no effect on my integration test (https://github.com/near/workspaces-rs/). Also, the latest link I think is https://docs.rs/env_logger/0.9.0/env_logger/index.html#capturing-logs-in-tests. – Ryan Aug 08 '22 at 22:22
6

In addition to Danilo Bargen's comment, you can write it in a shorter form:

use std::sync::Once;

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

fn setup() {
  INIT.call_once(env_logger::init);
}
5

For now, you can just re-initialize the logger at the top of every test and ignore the error. It's not a pretty solution but it works and is perfectly safe.

let _ = env_logger::init();

// your test code...
Steven
  • 5,654
  • 1
  • 16
  • 19
  • 2
    This no longer works, you get an panic when calling init multiple times: `panicked at 'env_logger::init should not be called after logger initialized: SetLoggerError(())` – Alex M Apr 29 '20 at 04:31
  • 1
    Maybe `try_init`? – Genarito Feb 23 '22 at 18:01