From 13408e79b940e9a33ca593ed30d1b20c54e01234 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 30 Jun 2026 18:05:05 +0800 Subject: feat(docs): add Chinese and English documentation for Mingling tutorials Add comprehensive documentation covering Declare a Dispatcher, Declare a Chain, Rendering Results, Multi-Command Program, Argument Parsing with Picker and Clap, Program Setup, Error Handling, Help Info, Resource System, Exit Code Control, Hook System, Testing, Completion, Structural Rendering, and Core Concepts --- docs/pages/14-testing.md | 129 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/pages/14-testing.md (limited to 'docs/pages/14-testing.md') diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md new file mode 100644 index 0000000..789da96 --- /dev/null +++ b/docs/pages/14-testing.md @@ -0,0 +1,129 @@ +

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 +

-- cgit