Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Trait method — The Template method under test, including whether the error path is being tested.
  2. Data structure — The Rust container type, defined as the test case name.
  3. 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
}