aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/14-testing.md
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-30 18:05:05 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-30 18:05:05 +0800
commit13408e79b940e9a33ca593ed30d1b20c54e01234 (patch)
tree282549991a3f31791401ca2f3255b9318679d2e9 /docs/pages/14-testing.md
parent29867ab5c0b40378a33318d989c809f90fc7d3aa (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.md129
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>