In this lesson, you will learn how to verify that your macros produce the intended code—and correctly fail when given invalid input—using the trybuild crate.
When you write a procedural or declarative macro, you aren't just writing logic; you are writing a source-code generator. Testing this requires more than standard unit tests because you need to ensure the expanded code is syntactically valid and behaves as expected.
Testing for Success
The most straightforward approach is to write "happy path" tests. Create a directory (typically tests/) and add a Rust file that invokes your macro. If the file compiles successfully when running cargo test, your macro output is valid.
// tests/macro_success.rs
use my_crate::my_macro;
my_macro! {
// Valid input that produces valid output
struct ValidStruct;
}
fn main() {
let _ = ValidStruct;
}
Testing for Failure
The real power of testing macros lies in "compile-fail" testing. You want to ensure that if a user provides incorrect input to your macro, the compiler issues a clear error rather than crashing or generating cryptic gibberish.
The trybuild crate automates this. You create a test that attempts to compile a piece of code meant to trigger a failure, then compare the compiler's output against a .stderr file.
#[test]
fn test_macro_fails_with_invalid_input() {
let t = trybuild::TestCases::new();
// This file should contain input that triggers a macro error
t.compile_fail("tests/macro_fail_case.rs");
}
In tests/macro_fail_case.rs, you might pass an invalid token, such as an i32 where an identifier is expected. When you run cargo test, trybuild compiles the file, captures the compiler error, and asserts that it matches the expected error signature. This prevents your macro from accidentally introducing breaking changes in error messages or failing silently when it should be rejecting bad input.
By testing the boundaries of what your macro accepts, you treat your macro-generated code with the same rigor as your manually written logic, ensuring your domain-specific syntax remains robust and user-friendly.
Key Takeaway: Use compile-fail testing to ensure your macros produce valid code under correct inputs and provide helpful, predictable error messages when inputs are invalid.