Skip to main content

Testing in Phlow

Phlow provides a built-in testing framework that allows you to write and run tests directly in your .phlow files. This documentation covers how to write, run, and understand tests in Phlow.

tip

For practical examples with real test outputs, see the Basic Testing Examples section.

Running Testsโ€‹

To run tests in a Phlow file, use the --test or -t flag:

phlow --test main.phlow

When you run tests, Phlow will:

  1. Load the Phlow file
  2. Download any required modules
  3. Execute each test case
  4. Display the results with a summary

Filtering Testsโ€‹

You can filter tests by description using the --test-filter flag. This will only run tests whose describe field contains the specified substring:

# Run only tests with "addition" in their description
phlow --test --test-filter "addition" main.phlow

# Run only tests with "user" in their description
phlow --test --test-filter "user" main.phlow

Example with test descriptions:

name: Calculator Test Suite
version: 1.0.0
description: Comprehensive calculator tests

tests:
- describe: "addition with positive numbers"
main: { operation: "add", a: 5, b: 3 }
payload: null
assert: !phs payload == 8
- describe: "addition with negative numbers"
main: { operation: "add", a: -5, b: 3 }
payload: null
assert: !phs payload == -2
- describe: "subtraction basic test"
main: { operation: "subtract", a: 10, b: 4 }
payload: null
assert: !phs payload == 6

steps:
- assert: !phs main.operation == "add"
then:
payload: !phs main.a + main.b
- assert: !phs main.operation == "subtract"
then:
payload: !phs main.a - main.b

Running with filter:

# This will only run the two addition tests
phlow --test --test-filter "addition" calculator.phlow

Output:

๐Ÿงช Running 2 test(s) matching 'addition' (out of 3 total)...

Test 1: addition with positive numbers - โœ… PASSED - Assertion passed: {{ payload == 8 }}
Test 2: addition with negative numbers - โœ… PASSED - Assertion passed: {{ payload == -2 }}

๐Ÿ“Š Test Results:
Total: 2
Passed: 2 โœ…
Failed: 0 โŒ

๐ŸŽ‰ All tests passed!

Test Structureโ€‹

Tests are defined in the tests section of your Phlow file. Each test case consists of:

  • main: Input values for the main context
  • payload: Initial payload value (optional)
  • assert: Expression-based assertion using PHS
  • assert_eq: Direct value comparison assertion

Basic Test Exampleโ€‹

name: Basic Math Test
version: 1.0.0
description: Testing basic arithmetic operations

tests:
- main:
x: 10
y: 20
payload: 5
assert: !phs payload == 35
- main:
x: 0
y: 0
payload: 100
assert: !phs payload == 100
- main:
x: -5
y: 5
payload: 10
assert: !phs payload > 0

steps:
- payload: !phs main.x + main.y + payload

Test Outputโ€‹

When you run the above test, you'll see output like this:

[2025-07-15T00:07:55Z INFO  phlow::loader] Downloading modules...
[2025-07-15T00:07:55Z INFO phlow::loader] All modules downloaded and extracted successfully
๐Ÿงช Running 3 test(s)...

Test 1: โœ… PASSED - Assertion passed: {{ payload == 35 }}
Test 2: โœ… PASSED - Assertion passed: {{ payload == 100 }}
Test 3: โœ… PASSED - Assertion passed: {{ payload > 0 }}

๐Ÿ“Š Test Results:
Total: 3
Passed: 3 โœ…
Failed: 0 โŒ

๐ŸŽ‰ All tests passed!

Assertion Typesโ€‹

Expression Assertions (assert)โ€‹

Expression assertions use PHS (Phlow Scripting) to evaluate conditions:

tests:
- main:
name: "John"
age: 25
payload: "active"
assert: !phs payload == "active"
- main:
count: 10
payload: 5
assert: !phs payload < main.count
- main:
items: [1, 2, 3]
payload: 3
assert: !phs payload == main.items.length

Direct Value Assertions (assert_eq)โ€‹

Direct value assertions compare the final payload with an expected value:

tests:
- main:
multiplier: 2
payload: 10
assert_eq: "Total is 20"
- main:
name: "Alice"
payload: "Hello"
assert_eq: "Hello Alice"

steps:
- payload: !phs main.multiplier * payload
- payload: !phs `Total is ${payload}`

Testing with Modulesโ€‹

You can test workflows that use modules:

name: HTTP Request Test
version: 1.0.0
description: Testing HTTP requests

modules:
- module: http_request
version: latest

tests:
- main:
url: "https://httpbin.org/json"
payload: null
assert: !phs payload.slideshow != null

steps:
- http_request:
url: !phs main.url
method: GET

Complex Test Scenariosโ€‹

Testing Conditional Logicโ€‹

name: Age Verification Test
version: 1.0.0
description: Testing age verification logic

tests:
- main:
age: 25
payload: null
assert: !phs payload == "Adult"
- main:
age: 16
payload: null
assert: !phs payload == "Minor"
- main:
age: 18
payload: null
assert: !phs payload == "Adult"

steps:
- assert: !phs main.age >= 18
then:
payload: "Adult"
else:
payload: "Minor"

Testing Data Transformationโ€‹

name: Data Processing Test
version: 1.0.0
description: Testing data transformation

tests:
- main:
users: [
{"name": "John", "age": 30},
{"name": "Jane", "age": 25}
]
payload: null
assert: !phs payload.length == 2
- main:
users: [
{"name": "Bob", "age": 35}
]
payload: null
assert: !phs payload[0].status == "processed"

steps:
- payload: !phs main.users.map(user => ({ ...user, status: "processed" }))

Test Failuresโ€‹

When tests fail, you'll see detailed error messages:

๐Ÿงช Running 2 test(s)...

Test 1: โŒ FAILED - Expected Total is 20, got Total is 30
Test 2: โœ… PASSED - Assertion passed: {{ payload == "Total is 15" }}

๐Ÿ“Š Test Results:
Total: 2
Passed: 1 โœ…
Failed: 1 โŒ

โŒ Some tests failed!

Test Best Practicesโ€‹

1. Use Descriptive Test Namesโ€‹

name: User Registration Validation
description: Tests for user registration validation rules

tests:
- main:
email: "user@example.com"
password: "secure123"
payload: null
assert: !phs payload.valid == true

2. Test Edge Casesโ€‹

tests:
- main:
value: 0
payload: null
assert: !phs payload == "zero"
- main:
value: -1
payload: null
assert: !phs payload == "negative"
- main:
value: null
payload: null
assert: !phs payload == "null"

3. Test Error Conditionsโ€‹

tests:
- main:
input: ""
payload: null
assert: !phs payload.error == "Input cannot be empty"
- main:
input: "invalid"
payload: null
assert: !phs payload.error != null

Advanced Testing Featuresโ€‹

Testing Asynchronous Operationsโ€‹

name: Async Operation Test
version: 1.0.0
description: Testing asynchronous operations

modules:
- module: http_request
version: latest
- module: sleep
version: latest

tests:
- main:
delay: 1
payload: "start"
assert: !phs payload == "completed"

steps:
- sleep:
seconds: !phs main.delay
- payload: "completed"

Testing Multiple Scenariosโ€‹

name: Calculator Test Suite
version: 1.0.0
description: Comprehensive calculator tests

tests:
# Addition tests
- main: { operation: "add", a: 5, b: 3 }
payload: null
assert: !phs payload == 8
- main: { operation: "add", a: -5, b: 3 }
payload: null
assert: !phs payload == -2

# Subtraction tests
- main: { operation: "subtract", a: 10, b: 4 }
payload: null
assert: !phs payload == 6
- main: { operation: "subtract", a: 0, b: 5 }
payload: null
assert: !phs payload == -5

# Multiplication tests
- main: { operation: "multiply", a: 6, b: 7 }
payload: null
assert: !phs payload == 42
- main: { operation: "multiply", a: -3, b: 4 }
payload: null
assert: !phs payload == -12

steps:
- assert: !phs main.operation == "add"
then:
payload: !phs main.a + main.b
- assert: !phs main.operation == "subtract"
then:
payload: !phs main.a - main.b
- assert: !phs main.operation == "multiply"
then:
payload: !phs main.a * main.b

Debugging Failed Testsโ€‹

When tests fail, you can debug them by:

  1. Running the flow normally to see the actual output:

    phlow main.phlow
  2. Adding debug information to your steps:

    steps:
    - log: !phs `Debug: main = ${JSON.stringify(main)}`
    - log: !phs `Debug: payload = ${JSON.stringify(payload)}`
    - payload: !phs main.x + main.y + payload
  3. Using the --show-steps flag to see step execution:

    phlow --show-steps --test main.phlow

Testing CLI Applicationsโ€‹

For CLI applications, you can test argument processing:

name: CLI Application Test
version: 1.0.0
description: Testing CLI argument processing

main: cli
modules:
- module: cli
with:
args:
- name: name
description: User name
index: 1
type: string
required: true
- name: age
description: User age
index: 2
type: number
required: true

tests:
- main:
name: "John"
age: 25
payload: null
assert: !phs payload == "John (25 years old)"
- main:
name: "Jane"
age: 30
payload: null
assert: !phs payload == "Jane (30 years old)"

steps:
- payload: !phs `${main.name} (${main.age} years old)`

Integration with CI/CDโ€‹

You can integrate Phlow tests into your CI/CD pipeline:

#!/bin/bash
# test-runner.sh

# Run all tests in the project
for test_file in tests/*.phlow; do
echo "Running tests in $test_file"
if ! phlow --test "$test_file"; then
echo "Tests failed in $test_file"
exit 1
fi
done

echo "All tests passed!"

Common Testing Patternsโ€‹

Setup and Teardownโ€‹

name: Database Test
version: 1.0.0
description: Testing database operations

modules:
- module: postgres
version: latest

tests:
- main:
table: "users"
data: {"name": "John", "email": "john@example.com"}
payload: null
assert: !phs payload.success == true

steps:
# Setup
- postgres:
query: "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(100), email VARCHAR(100))"

# Test operation
- postgres:
query: !phs `INSERT INTO users (name, email) VALUES ('${main.data.name}', '${main.data.email}')`

# Verify result
- postgres:
query: !phs `SELECT * FROM users WHERE email = '${main.data.email}'`
- payload: !phs { success: payload.length > 0 }

# Teardown
- postgres:
query: "DROP TABLE IF EXISTS users"

This comprehensive testing framework makes it easy to ensure your Phlow applications work correctly and reliably across different scenarios and inputs.