Testing OpenAF code with ow.test
This guide explains how to write and organize tests for your own OpenAF scripts and oJobs using the built-in ow.test module. It is adapted from the testing conventions used by the OpenAF project itself to test the runtime, focused on the parts that are directly reusable in any OpenAF-based project.
Test framework basics
OpenAF ships a built-in test utility at ow.test. Load it with ow.loadTest() at the top of any test file.
Core API
ow.loadTest();
// Assert equality (throws on mismatch and marks the test as failed)
ow.test.assert(actual, expected, "Descriptive failure message");
// Control output
ow.test.setOutput(true); // print pass/fail per assertion (default: true)
ow.test.setShowStackTrace(true); // include Java stack traces on failure
// Profiling
ow.test.profile("operationName", function() {
someExpensiveOperation();
});
var avg = ow.test.getProfileAvg("operationName"); // average time in ms
var max = ow.test.getProfileMax("operationName"); // max time in ms
// Counters (read after tests complete)
ow.test.getCountTest(); // total tests registered
ow.test.getCountPass(); // tests that passed
ow.test.getCountFail(); // tests that failed
ow.test.getCountAssert(); // total assert() calls executed
ow.test.reset(); // reset all counters
See ow_test.md for the full ow.test API reference, including per-key profiling (getProfileHits/getProfileAvg/getProfileLast/getProfileMax/getProfileMin/profileReset), getAllProfileHits/getAllProfileAvg/getAllProfileLast, and result export via toMarkdown()/toJUnitXML().
Minimal test file example
// Copyright 2024 Your Name
(function() {
exports.testMyModuleBasic = function() {
// ow.test is already loaded by the framework — call assert directly
ow.loadFormat(); // load any OpenAF module you need for the test
var result = ow.format.toHumanSize(1024);
ow.test.assert(result, "1 KB", "toHumanSize should format 1024 bytes as 1 KB");
};
exports.testMyModuleEdgeCase = function() {
ow.loadFormat();
var result = ow.format.toHumanSize(0);
ow.test.assert(result, "0 B", "toHumanSize should return 0 B for zero input");
};
})();
Key points:
- Wrap everything in a self-executing
(function() { ... })()so variables do not leak into the global scope. - Export each test as
exports.testXxx = function() { ... }. - Each export function must be independent (no shared mutable state between tests).
- Call
ow.loadTest()once before running any tests — do not repeat it inside individual test functions if a shared orchestrator already loaded it. - Load any OpenAF modules your test needs (e.g.
ow.loadFormat(),ow.loadObj(),plugin("ZIP")) at the top of the test function itself.
Naming conventions
Export functions should start with test followed by a descriptive name in CamelCase:
exports.testZIP ✓
exports.testZIPStream ✓
exports.testMyModuleBasic ✓
exports.myHelperFunction ✗ (not discovered as a test by convention-based runners)
Group related tests into per-area files, e.g. tests.Cache.js, tests.ZIP.js, so a whole area can be run or skipped together.
Orchestrating tests with oJob
A common pattern is to drive tests from an oJob YAML file so each exported test function gets its own job and pass/fail reporting. This mirrors the pattern OpenAF itself uses to run its own test suite:
# Copyright 2024 Your Name
jobs:
# Initialise: load the JS test module once
- name: MyArea::Init
exec: |
ow.loadTest()
args.tests = require("tests.MyArea.js");
# One job per exported test function
- name: MyArea::Basic functionality
from: MyArea::Init
exec: |
try {
args.tests.testMyModuleBasic()
log("PASS: testMyModuleBasic")
} catch(e) {
logErr("FAIL: testMyModuleBasic - " + e)
}
- name: MyArea::Edge case
from: MyArea::Init
exec: |
try {
args.tests.testMyModuleEdgeCase()
log("PASS: testMyModuleEdgeCase")
} catch(e) {
logErr("FAIL: testMyModuleEdgeCase - " + e)
}
todo:
- MyArea::Init
- MyArea::Basic functionality
- MyArea::Edge case
After all jobs run, read the aggregate counters and write a report:
ow.loadTest()
log("Tests: " + ow.test.getCountTest() + ", Pass: " + ow.test.getCountPass() + ", Fail: " + ow.test.getCountFail())
io.writeFileString("test-results.md", ow.test.toMarkdown())
io.writeFileString("test-results.xml", ow.test.toJUnitXML("MySuite", "My Test Suite"))
Run tests locally
Any oJob YAML file that wires up your tests can be run directly:
ojob autoTest.yaml
# or, without ojob on PATH:
java -jar openaf.jar --ojob -e autoTest.yaml
CI integration
To fail a CI pipeline when tests fail, check the counters (or the JUnit XML output) after the run and exit non-zero:
ow.loadTest()
if (ow.test.getCountFail() > 0) {
printErr(ow.test.getCountFail() + " test(s) failed")
exit(1)
}
Most CI systems (GitHub Actions, GitLab CI, Jenkins) can consume the JUnit XML produced by ow.test.toJUnitXML() directly for a readable test report in the pipeline UI.
Minimal test file template (copy-paste ready)
// Copyright 2024 Your Name
// Tests for <module or function description>
(function() {
exports.testMyFuncBasic = function() {
// Load any OpenAF module you need
// ow.loadFormat();
// Arrange
var input = "hello";
var expected = "HELLO";
// Act
var result = myModule.myFunc(input);
// Assert
ow.test.assert(result, expected, "myFunc should uppercase the input");
};
exports.testMyFuncNull = function() {
var result = myModule.myFunc(null);
ow.test.assert(result, null, "myFunc should return null for null input");
};
exports.testMyFuncWithCleanup = function() {
var tmpFile = "myFunc.tmp";
try {
myModule.myFuncWriteFile(tmpFile, "data");
ow.test.assert(io.fileExists(tmpFile), true, "Output file should exist");
} finally {
io.rm(tmpFile);
}
};
})();
Common pitfalls
Tests that need network access
Guard with a connectivity check and skip gracefully when the network is unavailable:
exports.testMyHTTP = function() {
try {
$rest().get("http://example.com");
} catch(e) {
log("Skipping network test: " + e.message);
return;
}
// ... assertions ...
};
Set reasonable timeouts with $rest({ timeout: 5000 }).get(...).
Cleanup
- Clean up temporary files with
io.rm("path")at the end of the function, usingtry/finallyso cleanup still runs when an assertion throws. - Avoid hard-coded absolute paths; use paths relative to the test file’s working directory.
Viewing detailed results
ow.loadTest()
print(ow.test.toMarkdown()) // human-readable summary + per-test table
io.writeFileString("results.xml", ow.test.toJUnitXML()) // for CI dashboards