TLDR: If a macro defined in one test module can’t be found in scope from another test module you may need to add a #[macro_use] line to the first’s parent module declaration

I’ve been using Rust in anger for a number of months now, but macros are still mostly unexplored waters. Declaring a macro that I could use in tests to remove some boiler plate turned out to be quite a bit more time consuming that I’d expected.

The setup

Here’s my basic project layout,

lib.rs
a.rs
b.rs

So a couple of modules, each of which will contain a set of unit tests.

/// lib.rs
mod a;
mod b;
----
/// a.rs
// some code here
#[cfg(test)]
mod tests {
    // tests here
}
----
/// b.rs
// some code here
#[cfg(test)]
mod tests {
    // tests here
}

In addition module a also contains some common test utility code that can be imported and used by unit tests of other modules,

/// a.mod
#[cfg(test)]
pub mod test_utils {
    // really handy utilities
}

The problem

To cut down on boiler plate in test cases I wanted to write a macro in a::test_utils (it relates directly to code in the a module, so that seems like the natural place to define it) that I can make use of in my module b unit tests. First step, define the macro and add a #[macro_use] to the module definition:

# a.mod
#[cfg(test)]
#[macro_use]
pub mod test_utils {
    macro_rules! boiler_buster { ... }

    // really handy utilities
}

But, when I try to use the macro in my module b tests the macro seemingly isn’t in scope 😦

/// b.mod
#[test]
fn test_that_thing() {
    boiler_buster!(...)
}
# >> error: cannot find macro `boiler_buster` in this scope
# >> help: have you added the `#[macro_use]` on the module/import?

“Why yes I have added a #[macro_use] on the module thank you, any other suggestions? No…?”

Here’s where search results on the whole stopped providing working solutions. Combining macro_use and an import in b::tests (#[macro_use] use crate::a::test_utils;)? No. Importing the macro directly like you would a struct (use crate::a::test_utils::boiler_buster;)? No. What was going on?

The solution

Here’s what I eventually found. A #[macro_use] will expose any macros from that module to its parent module. So my macro_use line with the test_utils module definition was only making my macro visible to the module a. To make the macro available to any module that might want to use it I also needed to add a macro_use line to the module declaration in lib.rs, which then became,

/// lib.rs
#[macro_use]
mod a;
mod b;

Success! 😄

Good to know

It’s worth noting that line ordering matters when it comes to exposing macros, so this version of lib.rs would not work,

/// lib.rs
mod b;
#[macro_use]
mod a;

In this case the Rust compiler will try to load module b before loading the macro definition from module a and the macro won’t be found in scope.