Common function that is used both in `tests` and `src` not found

Viewed 107

I have a number of tests defined inside some of my modules with:

#[cfg(test)]
mod tests {
    ...

and a number of tests found inside my /tests directory. All of these happen to need a common function called get_local() that I have put inside a utils module

+src
 |-lib.rs
 |-utils.rs
 |-...
+tests
 |_common.rs

Now since this function is only used in case I run tests I thought I should add a #[cfg(test)] above it.

#[cfg(test)]
pub fn get_local() -> SomeResult { ... }

and then I make sure it is inside lib.rs:

pub mod utils;

When I try using the function in tests/common.rs though:

use my_crate::utils::get_local;

and run the tests with the test command I get the error: no get_local in utils. This error only goes away if I remove #[cfg(test)] from the declaration of get_local. Why does this happen?

2 Answers

Why does this happen?

#[cfg(test)] is only active when the crate is being compiled into a test binary (instead of a library or normal binary) — the same condition under which #[test] functions are compiled to be run rather than ignored. But for a tests/ test, the crate being compiled in test mode is not your library — it's a separate binary crate for the test, which Cargo sets up to depend on your library compiled normally.

Thus, tests/ tests may only test the "public API" of your library — they see it no differently than a dependent crate outside your library.

As a workaround, you can use #[doc(hidden)] instead of #cfg(test)] so that the function is always available but undocumented, or you can put it in a module which is compiled into the test as well as the main library (but it's tricky to write code that works in both cases).

As for the "why does this happen?", Kevin Reid already answered: code in tests/ are "Integration Tests" and, therefore, uses the regular version of your lib (not the test-compiled one). See Integration Testing.

You may use the approach bellow to have a common test module for a single crate. Note that, for Integration Tests, each file inside tests/ is compiled as an individual crate. You may still create modules there.

With that said, my approach to having common test code is to put them inside a "test_commons" module and use:

lib.rs:

#[cfg(test)]
mod test_commons;

The contents of test_commons.rs has nothing special -- functions are defined without any #[cfg(test)] annotation.

Then, to use it:

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_commons::{self,ContainerKind,Blocking};
...

For Integration Tests inside tests/ to share that same code, simply use:

#[path = "../../src/test_commons.rs"] mod test_commons;
Related