on
Procedural Macros, pt. 5: Test Cases
We are going to test the code from our previous post with a couple of different approaches.
synstructure::test_derive is a simple macro that evaluates whether our derive
function generates the expected expansion for the macro’s invocation. It will
form a part of our unit tests.
#[test]
fn test_getter_generation() {
synstructure::test_derive! {
derive_propertese {
struct Person {
name: String,
age: u32
}
}
expands to {
impl Person {
fn get_name(&self) -> &str {
self.name.as_str()
}
fn get_age(&self) -> u32 {
self.age
}
}
}
no_build
}
}We use the no_build option to disable checking whether the code within the
macro compiles. While the code we have provided in the example above is fairly
self-contained and would compile, no_build can be useful in other more
complicated scenarios.
Unfortunately, we can’t use a procedural macro within the same crate that
defines it. This means addition of unit tests that use our generated code would
fail. Instead, we are going to use crate
trybuild that provides a
nice test harness for procedural macros. trybuild can be used to check if test
cases fail compilation as well - a feature we are going to leverage later (when
we add the ability to skip the generation of getters for a field).
Let’s start by adding trybuild to our crates dev dependencies.
cargo add --dev trybuildLet’s now create a folder tests/ui inside our project’s root folder.
Additionally, let’s create a file compile_tests.rs within. Edit
compile_tests.rs to the following
#[test]
fn test_compile_errors() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/test_getters.rs");
}This code initializes the trybuild test harness. We then invoke it with a test
file that we expect to pass compilation. In contrast, to test for failure, we’d
invoke it with compile_fail. To complete the test case, we need to provide the
content for tests_getters.rs (placed in tests/ui).
#[macro_use]
extern crate propertese;
#[derive(propertese)]
struct Person {
name: String,
age: u32
}
fn main() {
let frank = Person {
name: "Frank".to_owned(),
age: 40
};
assert_eq!("Frank", frank.get_name());
assert_eq!(40, frank.get_age());
}With cargo test, our test harness will compile and run the code above -
expecting it to pass without errors. In the final installment to this series, we will add the
ability to skip the generation of a getter for a particular field - by the
inclusion of attributes on a field.