Proc macro tests
Proc macro testing requires a different approach from standard unit testing because the code under test runs at compile time.
Test structure
Tests live in a tests/ directory at the crate root, split into two modules:
tests/
├── compile.rs # trybuild pass/fail compilation tests
├── compile/
│ ├── pass/ # .rs files that must compile successfully
│ └── fail/ # .rs files that must fail to compile, with .stderr files
└── template.rs # runtime tests for the generated trait implementations
Compile tests
There are two types of compile test:
Pass tests — .rs files imported into the compile module. They are not executed but will
cause a test failure if they do not produce valid Rust under cargo check. These verify that the
macro generates correct code for supported input types.
Fail tests — .rs files that should not compile. The trybuild crate captures the
compiler’s error output and compares it against a .stderr file:
#![allow(unused)]
fn main() {
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/compile/fail/*.rs");
}
}
This ensures users receive helpful, stable error messages when the macro is applied incorrectly.
Updating .stderr files after a Rust version change
Rust may change compiler error message formatting between versions, causing fail tests to break. See How to update trybuild .stderr files for the procedure.
Template tests
The template module tests that the generated Template trait implementations work correctly at
runtime. simple_test_case is used to run the same assertions against multiple Rust container
types (structs, tuple structs, enums, etc.):
#![allow(unused)]
fn main() {
#[test_case("Struct"; "basic_struct")]
#[test_case("TupleStruct"; "tuple_struct")]
#[test]
fn try_template_method_works(container_type: &str) {
// ...
}
}
Tests are organized using the following hierarchy:
- Trait method — The
Templatemethod under test, including whether the error path is being tested. - Data structure — The Rust container type, defined as the test case name.
- Test case — Additional context to uniquely identify the test.
Following the hierarchy above leads to the following generic test case path:
#![allow(unused)]
fn main() {
template::tests::trait_method::data_structure_test_case
}