⚠️ mlld is pre-release
Join the discord if you have questions. Report bugs on GitHub.
Testing modules
mlld provides a native test system designed for the unique needs of LLM orchestration and dynamic workflows.
Quick Start
Create a modulename.test.mld file:
var @data = ["apple", "banana", "cherry"]
var @test_array_has_items = @data.length() > 0
var @test_includes_banana = @data.includes("banana")
var @test_first_item = @data[0] == "apple"
Run tests:
mlld test # All tests
mlld test utils/ # Tests in utils/
mlld test parser.test.mld # Specific file
Test Structure
Test Discovery
Tests are discovered automatically:
- Files ending in
.test.mld - Variables are seen as tests
- Variables evaluated as true pass; false fails
>> This variable is a test
var @test_basic_math = 2 + 2 == 4
>> This variable is not a test
var @helper_data = [1, 2, 3]
>> This is also a test
var @test_array_length = @helper_data.length() == 3
Test Results
Tests pass when the variable evaluates to true (or truthy):
var @test_passes = true
var @test_also_passes = "hello" >> Truthy string
var @test_fails = false
var @test_also_fails = "" >> Falsy string
Writing Tests
Basic Assertions
The test module provides these assertion functions that return booleans:
Basic Assertions
@eq(a, b)- Strict equality (===)@deepEq(a, b)- Deep equality for objects/arrays@ok(value)- Truthy check@notOk(value)- Falsy check
Comparison Assertions
@gt(a, b)- Greater than@gte(a, b)- Greater than or equal@lt(a, b)- Less than@lte(a, b)- Less than or equal
Container Assertions
@includes(container, item)- Check if string/array contains item@contains(haystack, needle)- Alias for includes (better for strings)@len(value)- Get length of string/array/object
Testing Functions
Test exe functions by calling them:
exe @greet(name) = `Hello, @name!`
exe @double(n) = js { return n * 2 }
var @test_greet_works = @greet("Alice") == "Hello, Alice!"
var @test_double_works = @double(5) == 10
var @test_double_zero = @double(0) == 0
Testing Commands
Test command execution by capturing output:
var @result = run {echo "test"}
var @test_echo_works = @result.trim() == "test"
>> Test JavaScript execution
var @js_result = js { return "computed" }
var @test_js_execution = @js_result == "computed"
Testing with File Operations
Test file loading and processing:
>> Assuming test data files exist
var @config = <test-config.json>
var @readme = <test-readme.md>
var @test_config_loaded = @config != null
var @test_has_title = @config.title == "Test Config"
var @test_readme_has_content = @readme.length() > 0
Testing Conditionals
Test when logic by checking outcomes:
var @user = {"role": "admin", "active": true}
var @result = ""
when [
@user.role == "admin" && @user.active => var @result = "admin-access"
@user.role == "user" => var @result = "user-access"
none => var @result = "no-access"
]
var @test_admin_access = @result == "admin-access"
Testing Loops and Iteration
Test for loops and foreach:
var @numbers = [1, 2, 3, 4, 5]
exe @square(n) = js { return n * n }
>> Test foreach transformation
var @squared = foreach @square(@numbers)
var @test_foreach_length = @squared.length() == 5
var @test_first_square = @squared[0] == 1
var @test_last_square = @squared[4] == 25
>> Test for loop collection
var @doubled = for @n in @numbers => js { return @n * 2 }
var @test_doubled_sum = @doubled[0] + @doubled[1] == 6 >> 2 + 4
Running Tests
Basic Commands
# Run all tests in project
mlld test
# Run tests matching pattern
mlld test auth # Files/paths containing "auth"
mlld test src/utils/ # All tests in directory
mlld test validation.test.mld # Specific test file
Test Output
Tests show results as they run:
Running tests...
src/utils
✓ validation.test.mld (12ms)
✓ user validation works
✓ email format check
✗ password strength
modules/auth
✓ auth.test.mld (8ms)
✓ login flow
✓ logout clears session
Tests: 4 passed, 1 failed (5 total)
Time: 23ms
Environment Variables
Load environment variables for tests:
# Load specific env file
mlld test --env .env.test
# Auto-loads .env and .env.test from current directory
mlld test
Test files can access allowed environment variables:
import { MLLD_API_KEY, MLLD_NODE_ENV } from @input
var @test_has_api_key = @MLLD_API_KEY != null
var @test_test_environment = @MLLD_NODE_ENV == "test"
Best Practices
Test Naming
Use descriptive test names:
>> ✅ Good - descriptive names
var @test_user_validation_requires_email = @validateEmail(@user.email)
var @test_password_must_be_8_characters = @checkPasswordLength(@password)
var @test_admin_can_delete_posts = @canDelete(@user, @post)
>> ❌ Bad - unclear names
var @test_validation = @validate()
var @test_user = @check(@user)
var @test_1 = @test()
Keep Tests Focused
Write focused tests that check one thing:
>> ✅ Good - one assertion per test
var @test_array_length = @data.length() == 3
var @test_first_item = @data[0] == "apple"
var @test_includes_banana = @data.includes("banana")
>> ❌ Bad - multiple assertions in one test
var @test_array_stuff = @data.length() == 3 && @data[0] == "apple" && @data.includes("banana")
Test Edge Cases
Include boundary conditions and edge cases:
exe @calculateDiscount(price, percent) = when [
@price <= 0 => 0
@percent < 0 => @price
@percent > 100 => 0
* => js { return @price * (100 - @percent) / 100 }
]
>> Test normal cases
var @test_normal_discount = @calculateDiscount(100, 10) == 90
>> Test edge cases
var @test_zero_price = @calculateDiscount(0, 10) == 0
var @test_negative_price = @calculateDiscount(-50, 10) == 0
var @test_negative_percent = @calculateDiscount(100, -5) == 100
var @test_over_100_percent = @calculateDiscount(100, 150) == 0
Use Helper Functions
Create reusable test helpers:
exe @assertEq(actual, expected) = js { return actual === expected }
exe @assertContains(container, item) = js { return container.includes(item) }
exe @assertLength(array, len) = js { return array.length === len }
>> Use helpers in tests
var @user = { name: "Alice", tags: ["admin", "user"], permissions: [1, 2, 3] }
var @test_user_name = @assertEq(@user.name, "Alice")
var @test_tags_include_admin = @assertContains(@user.tags, "admin")
var @test_permissions_count = @assertLength(@user.permissions, 3)
Integration with CI/CD
GitHub Actions
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install -g @mlld/cli
- run: mlld test
Environment Setup
Set up test environments in CI:
# Install dependencies
npm install -g @mlld/cli
# Set test environment variables
export MLLD_NODE_ENV=test
export MLLD_API_TIMEOUT=5000
# Run tests with coverage
mlld test --env .env.ci
Limitations
The current test system has these limitations:
- No mocking: External dependencies must be handled manually
- Sequential execution: Tests run one file at a time
- No setup/teardown: No built-in before/after hooks
- Shared environment: All tests in a file share the same variable scope
- No test isolation: Tests can affect each other within the same file
For complex testing needs, consider:
- Splitting tests into multiple files for isolation
- Using separate test data files
- Creating dedicated test modules with helper functions
- Mocking external dependencies with conditional logic