Testing Your Program

Writing unit tests for Chain and Renderer

A built-in benefit of the pipeline model is **testability**. A Chain is just a function that takes input and returns output; a Renderer is just a function that takes input and writes content — no global state magic, testing is straightforward. ## Testing Renderer Renderer is the easiest to test — call the function, assert the result: ```rust @@@pack!(ResultName = String); // Returns String instead of () #[renderer] fn render_name(name: ResultName) -> String { r_println!("Hello, {}!", *name); } #[test] fn test_render_name() { let result = render_name(ResultName::new("Alice".to_string())); assert_eq!(result, "Hello, Alice!\n"); } ``` Note the Renderer's return type changed to `-> String` — `#[renderer]` auto-converts `RenderResult` to whatever return type you specify (default is `()`). By returning `String`, you can directly assert on the output content. ## Testing Chain Testing a Chain is slightly more complex because its return value is `Next` (actually `impl Into>`). You'll need the assertion macros provided by the framework: ```rust @@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process}; @@@dispatcher!("hello", CMDHello => EntryHello); @@@pack!(ResultName = String); @@@pack!(ErrorNoName = ()); @@@#[chain] @@@fn handle_hello(args: EntryHello) -> Next { @@@ let name = args.inner.first().cloned().unwrap_or_default(); @@@ if name.is_empty() { @@@ ErrorNoName::default().to_render() @@@ } else { @@@ ResultName::new(name).to_render() @@@ } @@@} #[test] fn test_handle_hello_with_name() { let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into(); // Asserts this is a render result (not continuing the chain) assert_render_result!(chain_process); // Asserts member_id is ResultName assert_member_id!(chain_process, ResultName); // Unpacks the inner value let result_name = unpack_chain_process!(chain_process, ResultName); assert_eq!(result_name.inner, "Alice"); } ``` What the three test macros do: | Macro | Function | | ----------------------- | ----------------------------------------------------------------- | | `assert_render_result!` | Asserts Chain returned the render path (not continuing the chain) | | `assert_member_id!` | Asserts the return value's member ID is a certain type | | `unpack_chain_process!` | Unpacks the original type from ChainProcess | ## Constructing Data with the entry! Macro If `extra_macros` is enabled, you can use `entry!` to quickly construct an Entry: ```rust // Features: ["extra_macros"] @@@use mingling::{assert_member_id, unpack_chain_process}; @@@use mingling::macros::entry; @@@dispatcher!("hello", CMDHello => EntryHello); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_hello(args: EntryHello) -> Next { @@@ let name = args.inner.first().cloned().unwrap_or_default(); @@@ ResultName::new(name).to_render() @@@} #[test] fn test_with_entry_macro() { // entry! constructs an Entry from string literals let entry = entry!("--name", "Alice"); let chain_process = handle_hello(entry).into(); let result_name = unpack_chain_process!(chain_process, ResultName); assert_eq!(result_name.inner, "Alice"); } ``` ## Testing Resource Injection If a Chain uses resources, you need to provide resource instances in the test: ```rust @@@use mingling::{assert_render_result, unpack_chain_process}; @@@#[derive(Default, Clone)] @@@struct ResPrefix(String); @@@dispatcher!("hello", CMDHello => EntryHello); @@@pack!(ResultGreeting = String); @@@ #[chain] fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next { let name = args.inner.first().cloned().unwrap_or_default(); ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render() } #[test] fn test_handle_with_resource() { // Resources need to be passed manually in tests let result = handle_hello( EntryHello::new(vec!["World".to_string()]), &ResPrefix("Hello".to_string()), ); let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram); assert_eq!(greeting.inner, "Hello, World"); } ``` The pipeline model makes testing simple: each Chain and Renderer is a relatively independent function — construct input, assert output.

Written by @Weicao-CatilGrass