Testing
aux4 includes a markdown-based testing framework for writing and running tests against your commands.
Installation
Install aux4/test:
1> aux4 aux4 pkger install aux4/testUsage
Run Tests
1> aux4 test run [directory|file] [options]Options:
- --configFile <path> — Path to a config.yaml file for variable substitution
- --config <section> — Config section name to load variables from
- --aiConfig <section> — Config section for AI-powered validation (expect:ai, expect:ai:score)
- --group <name> — Run only a specific test group (can be repeated for multiple groups)
- --output <path> — Write test results as JSON to a file
Examples:
1# Run all tests in current directory
2> aux4 test run
3
4# Run a specific test file
5> aux4 test run test/greet.test.md
6
7# Run with config variables
8> aux4 test run --configFile config.yaml --config dev
9
10# Run a specific group
11> aux4 test run --group smoke
12
13# Save results to JSON
14> aux4 test run --output results.jsonAdd Tests
Programmatically add new tests to existing .test.md files:
1> aux4 test add --testFile <file> --level <heading_level> --name <name> [--file <filename>] [--execute <command>]Inspect Tests
Extract all files from a test scenario and save them to a directory. Useful for debugging test fixtures:
1> aux4 test inspect --testFile <file> --name <scenario_name> --dir <output_directory>Writing Tests with .test.md Files
aux4 tests are written in Markdown files with the .test.md extension. These files use a structured format with headings to define test scopes and code blocks for test execution and expectations.
Test Structure and Scope
Tests are organized hierarchically using Markdown headings:
- # (H1): Defines the main test scenario/suite
- ## (H2-H6): Defines nested test scenarios within the parent scope
Each heading level creates a new test scope that can contain:
- Setup/teardown commands
- Test files
- Execute/expect test pairs
Code Blocks
Execute Block
Defines the command to run:
1```execute
2echo "Hello World"
3```Expect Block
Defines the expected output with optional modifiers:
1```expect
2Hello World
3```Expect Modifiers
You can modify expect behavior using colon-separated modifiers:
- partial: Match substring instead of exact match
- ignoreCase: Case-insensitive matching
- regex: Treat expected value as regular expression
Modifiers can be combined:
1```expect:partial
2Hello
3```
4
5```expect:ignoreCase
6hello world
7```
8
9```expect:regex
10Hello.*World
11```
12
13```expect:partial:ignoreCase
14hello
15```
16
17```expect:regex:ignoreCase
18error code: [a-z]+\d+
19```JSON Modifier
Use expect:json to compare JSON output. Both the actual output and expected value are parsed as JSON before comparison, so formatting differences don't matter:
1```execute
2aux4 config get dev
3```
4
5```expect:json
6{
7 "host": "localhost",
8 "port": 3000
9}
10```Can be combined with other modifiers:
1```expect:json:partial
2{
3 "host": "localhost"
4}
5```AI Validation
Use expect:ai to validate output using an LLM. The expect block contains a natural language description of what the output should be. Requires the aux4/ai-agent package installed and --aiConfig flag:
1```execute
2aux4 greet --name Alice
3```
4
5```expect:ai
6The output should be a friendly greeting that includes the name Alice
7```1> aux4 test run --configFile config.yaml --aiConfig aiAI Scoring
Use expect:ai:score to score output on a numeric scale. Define the evaluation criteria and pass threshold:
1```execute
2aux4 greet --name Alice
3```
4
5```expect:ai:score
6name: friendliness
7eval: How friendly is this greeting?
8range: 1-5
9pass: 3
10```Fields:
- name — Score label shown in output
- eval — Evaluation prompt for the LLM
- range — Numeric scale (default: 1-5)
- pass — Minimum passing score (default: 3)
Text Similarity
Use expect:similar to compare output using deterministic text similarity metrics without AI:
1```execute
2aux4 greet --name Alice
3```
4
5```expect:similar
6metric: fuzzy
7pass: 0.9
8---
9Hello Alice! Welcome aboard.
10```Fields:
- metric — Similarity algorithm: fuzzy (Levenshtein, default), cosine (word vectors), jaccard (word set overlap)
- pass — Minimum similarity score from 0 to 1 (default: 0.8)
- After ---, the reference text to compare against
- Can also use file: path/to/reference.txt to load reference from a file
Can be combined with :ignoreCase:
Wildcard Patterns in Partial Matching
When using expect:partial, you can use special wildcard patterns:
- *?: Match any characters on a single line
- **: Match any characters across multiple lines (multiline wildcard)
1```execute
2echo "File: test.txt Size: 1024 bytes"
3```
4
5```expect:partial
6File: *? Size: *? bytes
7```
8
9```execute
10echo -e "Header: Processing\n\nMultiple lines\nof output\n\nFooter: Complete"
11```
12
13```expect:partial
14Header:**Footer: Complete
15```You can combine wildcards:
1```execute
2echo -e "File: test.txt\nSize: 1024 bytes\nModified: today"
3```
4
5```expect:partial
6File: *?**Modified: *?
7```Multiple Expect Blocks
A single execute command can have multiple expect blocks with different modifiers:
1```execute
2echo "[Line 1](lines_1): Hello World
3[Line 2](lines_2): Testing 123
4[Line 3](lines_3): Final result"
5```
6
7```expect:partial
8Hello World
9```
10
11```expect:partial
12Testing 123
13```
14
15```expect:regex
16Line \d+: Hello World
17```Error Block
Expect specific error output:
1```error
2Command not found
3```Error blocks support the same modifiers as expect blocks (partial, ignoreCase, regex).
Timeout Block
Configure a custom timeout for tests in milliseconds. This is useful for long-running commands:
1```timeout
25000
3```
4
5```execute
6sleep 2 && echo "Command completed"
7```
8
9```expect
10Command completed
11```If no timeout is specified, the default timeout is used.
Setup and Teardown Blocks
- beforeAll: Run once before all tests in the scope
- afterAll: Run once after all tests in the scope
- beforeEach: Run before each test in the scope
- afterEach: Run after each test in the scope
1```beforeAll
2mkdir temp
3```
4
5```afterAll
6rm -rf temp
7```File Block
Create temporary files for testing:
1```file:test.txt
2This is test content
3that spans multiple lines
4```File Scope
Files are scoped to their parent heading level and all nested tests within it. Files created at one level are:
- Available to all nested tests (deeper heading levels)
- Not available to sibling tests (same heading level)
- Not available to tests in other parent sections
1## Parent Scenario
2
3```file:parent-file.txt
4This file is created at parent level
5```
6
7### Nested Test - File Available
8
9```execute
10cat parent-file.txt
11```
12
13```expect
14This file is created at parent level
15```
16
17## Sibling Scenario - File NOT Available
18
19```execute
20cat parent-file.txt 2>/dev/null || echo "File not found"
21```
22
23```expect
24File not found
25```Flexible Backticks
When your test content contains markdown code blocks, you can use more than 3 backticks for the outer block. The test parser will correctly match the same number of backticks:
1````execute
2printf "Code block:\n\`\`\`javascript\nconsole.log(\"hello\");\n\`\`\`\n"
3````
4
5````expect
6Code block:
7```javascript
8console.log("hello");
9```
10````This works for all block types: execute, expect, error, file, timeout, and hooks.
Dataset Block
Datasets allow you to parameterize tests with JSON data. Each entry in the dataset generates a separate test run:
1```dataset
2file: users.json
3root: $.items
4key: name
5```
6
7```execute
8aux4 greet --name {{name}}
9```
10
11```expect:partial
12Hello {{name}}
13```Fields:
- file — Path to a JSON file
- root — JSONPath expression to extract an array from nested JSON (optional)
- key — Field name used for test labeling in output (optional)
Shorthand syntax for simple cases:
1```dataset:users.json
2```Given users.json:
1[
2 { "name": "Alice", "age": 30 },
3 { "name": "Bob", "age": 25 }
4]This generates two tests: one for Alice and one for Bob. Each entry's fields become available as {{fieldName}} variables.
Variable Substitution
Test files support {{variableName}} substitution in execute, expect, error, and file blocks. Variables come from:
- Config files — Pass --configFile config.yaml --config section to load variables
- Datasets — Each dataset entry's fields become variables
1> aux4 test run --configFile config.yaml --config devGiven config.yaml:
1config:
2 dev:
3 host: localhost
4 port: 3000You can use {{host}} and {{port}} in your tests:
1```execute
2curl http://{{host}}:{{port}}/health
3```
4
5```expect
6ok
7```Test Suites
Group test files into suites using test.suite.md files. Suites let you organize tests into named groups and run specific groups with the --group flag:
1## smoke
2- health.test.md
3- auth.test.md
4
5## integration
6- api.test.md
7- database.test.md
8
9## performance (optional)
10- load.test.mdGroups marked (optional) only run when explicitly requested:
1# Run all non-optional groups
2> aux4 test run
3
4# Run only smoke tests
5> aux4 test run --group smoke
6
7# Run multiple groups
8> aux4 test run --group smoke --group integration
9
10# Run optional group
11> aux4 test run --group performanceExample Test File
1# Echo Tests
2
3## Basic functionality
4
5```beforeAll
6mkdir -p test-output
7```
8
9```afterAll
10rm -rf test-output
11```
12
13### Exact match
14
15```execute
16echo "Hello, World!"
17```
18
19```expect
20Hello, World!
21```
22
23### Case insensitive matching
24
25```execute
26echo "Hello, World!"
27```
28
29```expect:ignoreCase
30hello, world!
31```
32
33### Partial matching
34
35```execute
36echo "The quick brown fox jumps"
37```
38
39```expect:partial
40quick brown
41```
42
43### Regular expression matching
44
45```execute
46echo "Version 1.2.3"
47```
48
49```expect:regex
50Version \d+\.\d+\.\d+
51```
52
53## Error handling
54
55```execute
56nonexistent-command
57```
58
59```error:partial
60command not found
61```Adding Tests with the Add Command
The aux4 test add command allows you to programmatically add new tests to existing .test.md files. This is useful for automation or when you want to quickly scaffold new test cases.
When you run aux4 test add, it executes the command and automatically includes an expect block with the current output.
Command Syntax
1> aux4 test add --testFile <test_file> --level <heading_level> --name <test_name> [options]Parameters
- --testFile: Path to the .test.md file where the test should be added
- --level: Heading level (1-6) that determines the test scope hierarchy
- --name: Name of the test (will become the heading text)
- --file <filename>: Add file blocks to the test (can be used multiple times). Reads the content of existing files.
- --execute <command>: Add an execute block with the specified command
Examples
The add command generates markdown content that follows the test file structure. Here are examples of what gets generated:
Simple test with execution
1> aux4 test add --testFile my.test.md --level 2 --name "Test echo command" --execute "echo 'Hello World'"1## Test echo command
2
3```execute
4echo 'Hello World'
5```
6
7```expect
8Hello World
9```Test with file setup
1> aux4 test add --testFile api.test.md --level 2 --name "Test API endpoint" --file config.json --file data.txt --execute "curl -X POST localhost:3000/api"The --file option reads the content of existing files and adds them to the test as file blocks:
1## Test API endpoint
2
3```file:config.json
4{
5 "apiUrl": "http://localhost:3000",
6 "timeout": 5000
7}
8```
9
10```file:data.txt
11sample data content
12```
13
14```execute
15curl -X POST localhost:3000/api
16```
17
18```expect
19{"status": "ok"}
20```Nested test
1> aux4 test add --testFile integration.test.md --level 3 --name "Error handling" --execute "invalid-command"1### Error handling
2
3```execute
4invalid-command
5```
6
7```expect
8command not found
9```The add command will insert the new test at the appropriate location in the file based on the heading level, maintaining the hierarchical structure of your test file.
Testing aux4 Commands
You can test aux4 commands themselves using .test.md files. This is particularly useful for testing custom aux4 plugins or validating aux4 behavior.
Setting up aux4 Commands for Testing
To test aux4 commands, you typically need a .aux4 file that defines the command behavior. Here's an example of testing a simple greeting command:
1# aux4 Hello Command Test
2
3## Setup aux4 command
4
5```file:.aux4
6{
7 "profiles": [
8 {
9 "name": "main",
10 "commands": [
11 {
12 "name": "hello",
13 "execute": [
14 "echo \"Hello, ${name}\""
15 ],
16 "help": {
17 "text": "Simple greeting command",
18 "variables": [
19 {
20 "name": "name",
21 "text": "Name to greet"
22 }
23 ]
24 }
25 }
26 ]
27 }
28 ]
29}
30```
31
32### Test greeting command
33
34```execute
35aux4 hello --name John
36```
37
38```expect
39Hello, John
40```
41
42### Test with different name
43
44```execute
45aux4 hello --name "Alice Smith"
46```
47
48```expect
49Hello, Alice Smith
50```