liveBook · Manning

White-box testing

As explained in Write Powerful Rust Macros

White-box testing evaluates software with knowledge of and access to its internal implementation, including private code paths.

White-box testing verifies code with access to its internals. In Rust, tests placed alongside an implementation can inspect private structs, fields, and functions, making the technique useful for checking a procedural macro’s intermediate behavior rather than only its externally visible result. section 6.3 section 6.9

Start with a small assertion over a helper’s output, then strengthen it only where precision matters. Generated token streams can be checked by searching their string form, comparing them with an expected stream produced by `quote!`, or parsing output with `syn::parse2` and asserting on the resulting syntax tree. section 6.3

Testing generated builder output

Pass `struct StructWithNoFields {}` to `create_builder`, then assert that the output contains `StructWithNoFieldsBuilder`. A stricter test compares the generated stream against `quote! { struct StructWithNoFieldsBuilder {} }`; a structural alternative parses the output and checks the derived identifier. section 6.3

Use white-box tests deliberately:

  • Test private pure functions and transformations directly, where controlled inputs and outputs make coverage straightforward. section 6.5
  • Use them to isolate tricky macro errors or verify behavior that matters independently of an end-to-end compilation check. section 6.9
  • Avoid asserting incidental output layout: small internal changes can break a white-box test without changing meaning. section 6.5 section 6.4
  • Pair them with black-box compilation tests, which validate required behavior from the outside and are generally less brittle. section 6.5 section 6.9
Listing 6.5 Our builder-code with a first, basic test
// imports

pub fn create_builder(item: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse2(item).unwrap();
    let name = ast.ident;
    let builder = format_ident!("{}Builder", name);       #1

    quote! {
        struct #builder {}
    }
}

#[cfg(test)]
mod tests {            #2
    use super::*;

    #[test]
    fn builder_struct_name_should_be_present_in_output() {
        let input = quote! { #3
            struct StructWithNoFields {}
        };           

        let actual = create_builder(input);

        assert!(actual.to_string()
            .contains("StructWithNoFieldsBuilder"));     #4
    }
}

A minimal Rust unit test demonstrates colocating a test with macro implementation code, constructing input with `quote!`, invoking the helper, and checking generated output. section 6.3

How Write Powerful Rust Macros covers it

How deep it goes
Demonstrates string, exact-stream, and parsed-AST assertions, while noting the maintenance cost of macro-specific fixtures.
Examples
Uses a derive-generated builder for `StructWithNoFields` and exercises testing structs with fields and unnamed-field failures.
What it assumes
Assumes enough Rust to work with procedural macros, `quote!`, token streams, and test modules.
What you can do after
Diagnose/debug with it by directly asserting generated output and parsing it to inspect selected syntax details.
How it connects
Connects procedural macros with unit testing, black-box testing, pure functions, integration tests, TokenStream, and syntax trees.

Also explained in

Compare how 3 books explain white-box testing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.