BoxLang ๐ A New JVM Dynamic Language Learn More...
Copyright Since 2005 TestBox by Luis Majano and
Ortus Solutions, Corp
www.testbox.run | www.ortussolutions.com
Professional BDD (Behavior-Driven Development) and TDD (Test-Driven Development) testing framework for BoxLang and CFML applications. TestBox provides a comprehensive testing ecosystem with integrated mocking capabilities, multiple output formats, and both CLI and web-based test runners.
describe(), it()) and xUnit
(setup(), test*()) styles@browser annotation and bx-playwright (BoxLang)--failed) or the web runnersDrive a real browser (Chromium, Firefox or WebKit) right from your
specs. Add the @browser annotation (or
@browserProfile / @baseURL) to any spec, or
extend BrowserSpec, call browse(), and
assert on what your users actually see. Powered by bx-playwright and BoxLang.
Deploy bx-playwright to the same BoxLang web server that
runs TestBox. TestBox installs the profile's browser through that
deployed module the first time it is used; a separate OS-level BoxLang
installation is not required. The first run needs network access and
may take longer. Set @browserAutoInstall( false ) to
require a pre-installed browser, or call
ensureBrowserInstalled() explicitly from
beforeAll(). On Linux, install required system libraries
in the host or container; TestBox only downloads browser files.
@baseURL( "http://localhost:8090" )
@browserProfile( "ci" )
class extends="testbox.system.BaseSpec" {
function run() {
describe( "Ortus Shop", () => {
it( "signs in and shows the dashboard", () => {
browse( ( page ) => {
page.visit( "/" )
.fill( "Email", "[email protected]" )
.fill( "Password", "secret" )
.click( "Sign in" )
expect( page ).toHavePath( "/dashboard.html" )
expect( page ).toSee( "Welcome, Luis" )
} )
} )
} )
}
}
When a browser spec fails, TestBox attaches a screenshot, a Playwright trace and a video right on the failing spec in your report:
Open the trace with bxplaywright show-trace and step
through every action, the DOM, the console and the network:
Fix it, then rerun only what failed with --failed or the
Run Failed button. Learn more in the Browser
Testing guide.
# Install TestBox and CLI tools via CommandBox
box install testbox testbox-cli
# Or install bleeding edge
box install testbox@be testbox-cli
// tests/specs/UserServiceTest.cfc
class extends="testbox.system.BaseSpec" {
function run() {
describe( "UserService", () => {
beforeEach( () => {
userService = new models.UserService()
} )
it( "should create a new user", () => {
var user = userService.createUser( "[email protected]", "John Doe" )
expect( user.getEmail() ).toBe( "[email protected]" )
expect( user.getName() ).toBe( "John Doe" )
} )
} )
}
}
class extends="testbox.system.BaseSpec" {
function run() {
describe( "Payment Service", () => {
beforeEach( () => {
// Create mocks and spies
mockGateway = createMock( "services.PaymentGateway" )
mockLogger = createEmptyMock( "cblogger.models.Logger" )
// Setup mock behavior
mockGateway.$( "processPayment" ).$results( {
success: true,
transactionId: "TXN-12345"
} )
paymentService = new models.PaymentService(
gateway = mockGateway,
logger = mockLogger
)
} )
it( "should process payment and log success", () => {
var result = paymentService.charge( 100.00, "USD" )
// Verify method calls
expect( mockGateway.$times( 1, "processPayment" ) ).toBeTrue()
expect( mockLogger.$times( 1, "info" ) ).toBeTrue()
// Verify results
expect( result.success ).toBeTrue()
expect( result.transactionId ).toBe( "TXN-12345" )
} )
} )
}
}
class extends="testbox.system.BaseSpec" {
function run() {
describe("User Profile Tests", () => {
it("should handle various user data scenarios", () => {
// Generate realistic test data
var testUsers = mockData(
firstName = "fname",
lastName = "lname",
email = "email",
age = "age",
address = {
street = "streetaddress",
city = "city",
state = "state",
zipCode = "zipcode"
},
registrationDate = "datetime"
)
// Test with realistic data
for ( var user in testUsers ) {
var profile = userService.createProfile( user )
expect( profile.isValid() ).toBeTrue()
expect( profile.getEmail() ).toMatch( "^[\w\.-]+@[\w\.-]+\.[A-Za-z]{2,}$" )
}
})
})
}
}
# Via CommandBox CLI
box testbox run
# Via BoxLang CLI Runner (fastest execution)
./testbox/run # Run default tests.specs
./testbox/run --directory=my.tests # Specific directory
./testbox/run --bundles=my.bundle # Specific bundles
./testbox/run --reporter=json # Custom reporter
# Via web browser
# Navigate to: http://localhost/testbox/system/runners/HTMLRunner.cfm
class extends="testbox.system.BaseSpec" {
function run() {
describe( "User Registration", () => {
beforeEach(() => {
userService = createMock("models.UserService")
variables.sut = new handlers.Users()
})
describe("When registering a new user", () => {
it("should validate email format", () => {
expect(() => {
userService.register("invalid-email", "password")
}).toThrow("ValidationException")
})
it("should create user with valid data", () => {
var result = userService.register("[email protected]", "securePass")
expect(result.success).toBeTrue()
expect(result.user.email).toBe("[email protected]")
})
})
})
}
}
class extends="testbox.system.BaseSpec" {
function setup() {
// Runs before each test
userService = new models.UserService()
testData = {
email: "[email protected]",
name: "Test User"
}
}
function testUserCreation() {
var user = userService.createUser( testData.email, testData.name )
$assert.isEqual( testData.email, user.getEmail() )
$assert.isEqual( testData.name, user.getName() )
}
function testEmailValidation() {
$assert.throws( () => {
userService.createUser( "invalid-email", "Test User" )
}, "ValidationException" )
}
function tearDown() {
// Cleanup after each test
structDelete( variables, "userService" )
structDelete( variables, "testData" )
}
# Run all tests with default settings
box testbox run
# Run specific test bundles
box testbox run bundles=tests.specs.UserServiceTest
# Run tests with custom reporter
box testbox run reporter=json
# Run tests with labels ( focused testing )
box testbox run labels=unit --excludes=integration
# Watch mode for continuous testing
box testbox watch
# Generate test templates
box testbox create bdd MyNewTest
box testbox create unit MyNewTest
Create an Application.bx|cfc in your test directory:
class {
// Test application name
this.name = "MyApp-Tests"
// TestBox mappings
this.mappings[ "/testbox" ] = expandPath( "/testbox" );
this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() );
this.mappings[ "/models" ] = expandPath( "/models" );
}
Create tests/runner.bxm|cfm for web-based test execution:
<!DOCTYPE html>
<html>
<head>
<title>My Application Test Suite</title>
</head>
<body>
<bx:script>
// Create TestBox instance
testbox = new testbox.system.TestBox(
options = {
// Test bundle directories
bundles = [
"tests.specs"
],
// Directories to include/exclude
directory = {
mapping = "tests.specs",
recurse = true
},
// Test labels
labels = url.labels ?: "",
excludes = url.excludes ?: "",
// Reporter
reporter = url.reporter ?: "simple",
// Coverage settings
coverage = {
enabled = true,
pathToCapture = expandPath("/models"),
whitelist = "*.cfc",
blacklist = "*Test*.cfc"
}
}
)
// Run tests and output results
writeOutput( testbox.run() )
</bx:script>
</body>
</html>
Apache License, Version 2.0.
TestBox is maintained under the Semantic Versioning guidelines as much as possible.
Releases will be numbered with the following format:
<major>.<minor>.<patch>
And constructed with the following guidelines:
Join us in our Ortus Community and become a valuable member of this project TestBox BDD. We are looking forward to hearing from you!!
Apache License, Version 2.0. See LICENSE file for details.
TestBox is a professional open-source project by Ortus Solutions.
Copyright Since 2005 TestBox by Luis Majano and Ortus Solutions, Corp
www.testbox.run | www.ortussolutions.com
Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.
"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; And patience, experience; and experience, hope: And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the Holy Ghost which is given unto us." Romans 5:5
"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" John 14:1-12
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
resolveRetries() and a handful of reporter and runner helpers no longer use the elvis operator on struct members that may be missing, which left the variable undefined on Adobe 2023 without null support (Variable SPECRETRIES is undefined). They use structKeyExists() instead.Application (this.name) or TestBoxRunner, so specs that use the application or session scopes run from the CLI. BoxLang only starts an application for an Application.bx next to the running script, which the runner never had.getPageContext() outside of a web request: with the bx-web-support module installed it is defined in the BoxLang CLI runner too, where it throws.assertRouteIs(), not at coldbox/system/testing/BaseTestCase.cfc: frames under /coldbox/system/testing/ are treated like TestBox internals when choosing the origin, the code snippet and Open in editor.aiAssist, aiProviders, aiContextLines, aiStackFrames and aiPrompt reporter options.urlParams reporter option sets request params such as editor and aiAssist when a report is produced from code and there is no url scope.build/vendor rebuilds the inlined front-end libraries with one command (box run-script assets:update) and CI keeps them honest.AgentReporter (reporter=agent): a compact, token-efficient JSON reporter for AI agents and automation. It emits totals plus only failed/errored specs, with options detail, maxFailures, maxMessageLength, includeStack, stackDepth, includeSkipped and includeDebug.@browser, @browserProfile or @baseURL gets browse( callback, options ), browserAvailable(), ensureBrowserInstalled(), getBrowserSupport(), this.playwright() and the browser matchers, mixed in by the runner (public methods the spec declares itself are kept, private ones with the same name are replaced). @browser( false ) turns it off for a class whose parent turns it on. testbox.system.BrowserSpec is an optional base class that carries @browser. One browser per bundle, closed by the runner after the bundle even when an afterAll() throws (when a request aborts, the next run that opens a browser closes the one left behind), and the kept screenshots, trace and videos of a failed browse() attached to the spec. Specs skip with an install hint when bx-playwright is missing or the engine is not BoxLang. The logic lives in testbox.system.browser.BrowserSupport.testbox.system.browser.BrowserMatchers, registered for every browser bundle and usable from any BoxLang spec with addMatchers(): toHaveTitle(), toHaveURL(), toHavePath() and toSee() for pages, toHaveText(), toBeVisible(), toBeHidden(), toHaveCount() and toHaveValue() for locators, plus their not forms. They delegate to the retrying bx-playwright assertions, also when negated, and fail with the bx-playwright message.attach( path, type, name ) in every spec to attach files to the running spec, kept in the new attachments array of the spec stats for passed and failed specs. The JSON report includes them, the Simple report links them, the JUnit and ANT JUnit reports add a <system-out> with one [[ATTACHMENT|path]] line per file, and the text, console and stream outputs list them under failed specs.retries argument on it(), fit() and xit(), a retries bundle annotation, a retries method annotation for xUnit tests, and a global retries runner option (BoxLang runner --retries=N). The spec value wins over the bundle annotation, which wins over the global option. A failing or erroring spec reruns its beforeEach(), body and afterEach() (or setup(), test and teardown()) up to N more times and records the final attempt. Skipped specs are never retried. The new attempts spec stat counts the runs and the text, console, Simple and stream outputs show "(passed after N attempts)".TestResult.getFailedTargets() returns the bundles and spec ids that failed or errored in a run (bundles that failed outside of a spec go to bundleErrors and are not rerun). The Simple, Min, Dot and Doc reports show a Run Failed button next to Run All, a link built from the report itself, so web runners keep no state. The BoxLang runner writes {reportpath}/.testbox-failed.json on every run and ./run --failed reruns only those; when the file is missing or lists nothing, it prints a message and runs nothing.inlineImageMaxKB reporter option (default 2048, 0 turns it off) are embedded, larger ones stay links. A trace attachment offers a Copy show-trace command button. A failed spec's screenshots are embedded once, in its failure card.--web-server, --web-server-url and --web-server-timeout: start a web server command before the tests, wait until its URL answers, stop it and its child processes after the tests, and exit with code 1 when it does not answer in time. The URL becomes the default baseURL of browser bundles through server.testbox.webServerURL.Simple, Min, Dot, Doc) are rebuilt on Bootstrap 5.3, Bootstrap Icons, Alpine and Prism, with light and dark themes and the new TestBox logos. Everything is still inlined so reports run airgapped, and a page went from about 1.4 MB to about 0.45 MB.testBundles, testSuites or testSpecs, so a refresh runs it again. Every other runner option falls back to its default.Dot opens a spec's details in a drawer instead of alert(), Doc is a styled page with a bundle navigation, and the status chips of Min and Dot now filter.system/reports/ReportHelper.cfc and BaseHTMLReporter.cfc.Playwright.AssertionFailed exceptions now count as spec failures, like TestBox.AssertionFailed, in BDD specs and xUnit tests, keeping their message and detail. Other Playwright.* errors still count as errors.-1 error) when a bundle threw outside a spec, for example in beforeAll(). It now shows none.testbox.system.BaseSpec errored: the virtual BaseSpec missed its private state ($specStatsHolder), three of its methods declared a BaseSpec return type and expect() resolved Expectation relative to the spec. They run again.ensureBrowserInstalled() downloaded Chrome for Testing for a profile with a branded channel (chrome, msedge). It now skips the install, as the installed browser is used.testbox.log.run script now quotes its arguments, so runner options with spaces reach the BoxLang runner intact.JUnit and ANTJunit reporters failed on BoxLang without the bx-esapi module (encodeForXMLAttribute()). They now fall back to xmlFormat(), also on Adobe with full null support.url.fullPage switch of the HTML reporters. A report is always a complete page.https://testbox.ortusbooks.com/readme/release-history/whats-new-with-7.1.0
skip annotation support for skipping entire test classes.expect( value ).withContext( message ) that prepends semantic context to all failure messages including negated matchers and custom matchers.expectAny(), expectSome(), and expectNone() alongside existing expectAll() with detailed failure summaries including element index/key and pass count reporting.$assert.all(), assertAll() that run multiple assertion closures and report every failure at once instead of stopping at the first.toBeTruthy(), toBeFalsy(), toBeSameInstanceAs(), toHaveSize(), toThrowMatching(), toIncludeAll(), toIncludeAny(), and toIncludeNone().toBeASet(), toEqualSet(), toBeSubsetOf(), toBeSupersetOf(), toBeDisjointFrom(), toHaveUnion(), toHaveIntersection(), toHaveDifference(), and toHaveSymmetricDifference() for working with BoxLang Set objects.toBeRange(), toContainValue(), toContainRange(), toBeInRange(), toBeBeforeRange(), toBeAfterRange(), toBeBounded(), toBeUnbounded(), toBeHalfBounded(), toBeIterable(), toBeAscending(), toBeDescending(), toHaveStep(), and toClampTo() for BoxLang Range objects.toHavePath(), toHavePathValue(), toHavePathType(), toHavePathSatisfying(), path(), and queryPath() for navigating and asserting against nested BoxLang data structures using dot-notation, array indexes, wildcards, filters, and recursive descent.$assert.isTruthy(), $assert.isFalsy(), $assert.includesAll(), $assert.includesAny(), and $assert.includesNone().coverageEnabled URL parameter in the CFML test runner now defaults to false instead of true. Code coverage requires FusionReactor and is now opt-in. Pass ?coverageEnabled=true to restore the previous behavior.expectAll() failure messages to include pass/fail counts and per-element failure details with index/key context.$args() struct-order fragility and add Set/Range support to argument matching.actual.equals().GetPageContextResponse() error while running BoxLang in Adobe compatibility mode.KeyNotFoundException [url] crashing every CLI run on BoxLang 1.17+.param.isLucee() returning true on BoxLang, which broke engine detection helpers and engine-conditional skips.run runners so they use the calculated location paths.test(), xtest(), ftest() alias for more natuarl testing
$
box install testbox