diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-30 18:05:05 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-30 18:05:05 +0800 |
| commit | 13408e79b940e9a33ca593ed30d1b20c54e01234 (patch) | |
| tree | 282549991a3f31791401ca2f3255b9318679d2e9 /docs/pages/14-testing.md | |
| parent | 29867ab5c0b40378a33318d989c809f90fc7d3aa (diff) | |
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
Diffstat (limited to 'docs/pages/14-testing.md')
| -rw-r--r-- | docs/pages/14-testing.md | 129 |
1 files changed, 129 insertions, 0 deletions
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 @@ +<h1 align="center">Testing Your Program</h1> +<p align="center"> + Writing unit tests for Chain and Renderer +</p> + +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<ChainProcess<ThisProgram>>`). 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. + +<p align="center" style="font-size: 0.85em; color: clear;"> + Written by @Weicao-CatilGrass +</p> |
