3 minutes
Rust: Sharing macros between tests
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.