What's New With 7.1.0
September 10, 2026
TestBox 7.1.0 introduces a significant upgrade to the assertions and expectations library, adding grouped assertions, collection expectation modes, rich failure diagnostics, and a suite of new matchers inspired by JUnit 5 and Jasmine.
Expectation Context: withContext()
Add semantic context to any expectation so failure messages include identifying information. Works with all matchers, negated matchers, and custom matchers.
it( "validates a user record", () => {
expect( user.age )
.withContext( "user.age" )
.toBeGT( 0 )
expect( user.email )
.withContext( "user.email" )
.toMatch( "@" )
} )Collection Expectation Modes
Three new collection modes complement the existing expectAll().
expectAny()
Passes when at least one element in the collection passes the chained matcher.
expectSome()
Passes when a bounded number of elements pass. max = 0 means no upper bound.
expectNone()
Passes when zero elements pass the chained matcher.
Improved expectAll() Failure Messages
All collection modes now produce detailed failure summaries with pass/fail counts and per-element failure context including the element index or struct key.
Grouped Assertions: assertAll()
Run multiple assertion closures and report every failure at once — instead of stopping at the first. Non-assertion exceptions are rethrown immediately.
Using the $assert style:
New Matchers
toIncludeAll / toIncludeAny / toIncludeNone
Assert that a string or array contains all, any, or none of the given needles with case-insensitive matching.
toBeTruthy / toBeFalsy
Assert that a value is truthy (not false, 0, empty string, or null) or falsy.
toHaveSize
Alias for toHaveLength() — works on strings, arrays, structs, and queries.
toBeSameInstanceAs
Assert two references point to the exact same object instance.
toThrowMatching
Assert a function throws an exception that matches a predicate closure.
New Assertion BIFs
For xUnit-style testing, the following new $assert methods are available:
$assert.isTruthy( actual, message )
Value is truthy
$assert.isFalsy( actual, message )
Value is falsy
$assert.includesAll( target, needles, message )
Target contains every needle
$assert.includesAny( target, needles, message )
Target contains at least one needle
$assert.includesNone( target, needles, message )
Target contains no needle
$assert.all( executables, heading )
Run all assertions, report every failure
Set Expectations
TestBox now provides a comprehensive suite of set-related matchers for working with BoxLang Set objects. These matchers leverage the global setOf() function to create sets and provide powerful assertions for set operations.
Creating Sets with setOf()
toBeASet() / notToBeASet()
Assert that a value is (or is not) a Set object.
toEqualSet() / notToEqualSet()
Assert that two sets contain the same elements, regardless of order.
toBeSubsetOf() / notToBeSubsetOf()
Assert that all elements of the actual set are contained in the expected set.
toBeSupersetOf() / notToBeSupersetOf()
Assert that the actual set contains all elements of the expected set.
toBeDisjointFrom()
Assert that two sets share no common elements.
toHaveUnion() / notToHaveUnion()
Assert that the union of two sets equals an expected set.
toHaveIntersection() / notToHaveIntersection()
Assert that the intersection of two sets equals an expected set.
toHaveDifference() / notToHaveDifference()
Assert that the set difference (actual - expected) equals an expected result.
toHaveSymmetricDifference() / notToHaveSymmetricDifference()
Assert that the symmetric difference (elements in either set but not both) equals an expected result.
Real-World Example: Menu Selection
Range Expectations (BoxLang)
TestBox now includes a full set of matchers for BoxLang Range objects, including containment, ordering, bounds, and step/clamp assertions.
Core Range Matchers
toBeRange()toContainValue( value )toContainRange( range )toBeInRange( range )toBeBeforeRange( range )toBeAfterRange( range )
Range Shape And Direction
toBeBounded()toBeUnbounded()toBeHalfBounded()toBeIterable()toBeAscending()toBeDescending()
Step And Clamp
toHaveStep( step )toClampTo( value, expected )
Equivalent native Range API examples used under these matchers:
Data Navigator Expectations
TestBox now provides a suite of matchers that leverage BoxLang's built-in dataNavigate() BIF to safely navigate and assert against values in nested data structures. These matchers support dot-notation, array indexes, wildcards, filters, recursive descent, and all other JSONPath-style expressions.
toHavePath() / notToHavePath()
Assert that a path exists (or does not exist) in a nested data structure.
toHavePathValue() / notToHavePathValue()
Assert that the value at a path matches an expected value.
toHavePathType() / notToHavePathType()
Assert the type of the value at a path. Supports standard types (string, numeric, boolean, struct, array) and common aliases (str, num, bool, arr, obj, map).
toHavePathSatisfying() / notToHavePathSatisfying()
Assert that the value at a path satisfies a predicate closure.
path()
Navigate to a path and return a normal Expectation on the value at that path. Supports chaining any matcher on the result.
queryPath()
Navigate to a path and return an Expectation on an array of all matching values. Fans out at wildcards, filters, and recursive descent segments.
Real-World Example: API Response Validation
Class-Level skip Annotation
BDD test classes now support a class-level skip annotation, so an entire class can be skipped without touching each describe() or editing the runner's filters.
Skipped classes are reported as skipped rather than silently omitted, so the count stays honest.
You can also pass a reason:
MockBox $args() Improvements
$args() previously built its argument hash in a way that depended on struct iteration order. Two structurally identical structs built in a different order could hash differently, so a mock could fail to match arguments it should have matched and would return null instead.
This was always latent, but Lucee 7.1 changed its underlying map implementation and made it reproducible.
$args() now normalizes nested structures deterministically, so structurally-equal arguments match regardless of how they were built:
$args() also now understands BoxLang Set and Range objects when matching.
Summary
withContext()
Expectation
expect( v ).withContext( "label" ).toBe( x )
expectAny()
Collection
expectAny( arr ).toBeGT( 2 )
expectSome()
Collection
expectSome( arr, 2, 5 ).toBeGT( 2 )
expectNone()
Collection
expectNone( arr ).toBeEmpty()
assertAll()
Grouped
assertAll( closures, "heading" )
toBeTruthy()
Matcher
expect( v ).toBeTruthy()
toBeFalsy()
Matcher
expect( v ).toBeFalsy()
toBeSameInstanceAs()
Matcher
expect( a ).toBeSameInstanceAs( b )
toHaveSize()
Matcher
expect( v ).toHaveSize( 3 )
toThrowMatching()
Matcher
expect( fn ).toThrowMatching( p )
toIncludeAll()
Matcher
expect( v ).toIncludeAll( needles )
toIncludeAny()
Matcher
expect( v ).toIncludeAny( needles )
toIncludeNone()
Matcher
expect( v ).toIncludeNone( needles )
toBeASet() / nottoBeASet()
Set
expect( setOf( 1, 2 ) ).toBeASet()
toEqualSet() / notToEqualSet()
Set
expect( setOf( 1, 2 ) ).toEqualSet( setOf( 2, 1 ) )
toBeSubsetOf() / notToBeSubsetOf()
Set
expect( setOf( 1 ) ).toBeSubsetOf( setOf( 1, 2 ) )
toBeSupersetOf() / notToBeSupersetOf()
Set
expect( setOf( 1, 2 ) ).toBeSupersetOf( setOf( 1 ) )
toBeDisjointFrom()
Set
expect( setOf( 1 ) ).toBeDisjointFrom( setOf( 2 ) )
toHaveUnion() / notToHaveUnion()
Set
expect( setOf( 1 ) ).toHaveUnion( setOf( 2 ), setOf( 1, 2 ) )
toHaveIntersection() / notToHaveIntersection()
Set
expect( setOf( 1, 2 ) ).toHaveIntersection( setOf( 2, 3 ), setOf( 2 ) )
toHaveDifference() / notToHaveDifference()
Set
expect( setOf( 1, 2 ) ).toHaveDifference( setOf( 2 ), setOf( 1 ) )
toHaveSymmetricDifference() / notToHaveSymmetricDifference()
Set
expect( setOf( 1, 2 ) ).toHaveSymmetricDifference( setOf( 2, 3 ), setOf( 1, 3 ) )
toBeRange()
Range
expect( 1..10 ).toBeRange()
toContainValue()
Range
expect( 1..10 ).toContainValue( 5 )
toContainRange()
Range
expect( 1..10 ).toContainRange( 2..5 )
toBeInRange()
Range
expect( 5 ).toBeInRange( 1..10 )
toBeBeforeRange()
Range
expect( 0 ).toBeBeforeRange( 1..10 )
toBeAfterRange()
Range
expect( 11 ).toBeAfterRange( 1..10 )
toBeBounded()
Range
expect( 1..10 ).toBeBounded()
toBeUnbounded()
Range
expect( r ).toBeUnbounded()
toBeHalfBounded()
Range
expect( r ).toBeHalfBounded()
toBeIterable()
Range
expect( 1..10 ).toBeIterable()
toBeAscending()
Range
expect( 1..10 ).toBeAscending()
toBeDescending()
Range
expect( 10..1 ).toBeDescending()
toHaveStep()
Range
expect( r ).toHaveStep( 2 )
toClampTo()
Range
expect( 1..10 ).toClampTo( 20, 10 )
toHavePath() / notToHavePath()
Navigator
expect( data ).toHavePath( "app.name" )
toHavePathValue() / notToHavePathValue()
Navigator
expect( data ).toHavePathValue( "app.name", "TestApp" )
toHavePathType() / notToHavePathType()
Navigator
expect( data ).toHavePathType( "app.settings.port", "numeric" )
toHavePathSatisfying() / notToHavePathSatisfying()
Navigator
expect( data ).toHavePathSatisfying( "app.name", p -> p == "TestApp" )
path()
Navigator
expect( data ).path( "app.name" ).toBe( "TestApp" )
queryPath()
Navigator
expect( data ).queryPath( "users[*].name" ).toInclude( "Alice" )
Bug Fixes and Improvements
Code Coverage Is Now Opt-In
The coverageEnabled URL parameter in the CFML test runner now defaults to false instead of true.
Code coverage requires FusionReactor, so defaulting it on meant every plain runner hit paid for a feature most runs did not want. If you rely on coverage, enable it explicitly:
This is a behavioral change. If you were relying on the implicit default to collect coverage, you must now pass coverageEnabled=true or set it in your runner options.
Date Equality In Assertions
Equalize assertions now compare date and date/time objects by their instant rather than calling actual.equals() blindly. Comparing a java.util.Date against a CFML date string, or two date objects of different concrete types, now behaves as expected instead of failing on type identity.
Engine Detection
isLucee() returned true under BoxLang, because BoxLang registers a lucee server scope key for compatibility. Any spec branching or skipping on engine took the Lucee path when running on BoxLang. isLucee() now requires the absence of the boxlang key.
Simple Reporter HTML Escaping
Bundle and spec names are now HTML-encoded in the Simple reporter, so a test name containing markup no longer breaks the report layout.
Full Null Support
TestBox internals assumed a non-null runtime in several places, producing spurious null-reference errors on engines configured with full null support. Null handling was audited across the runners, coverage service and mock generator, and the configuration is now covered in CI.
BoxLang CLI Runner
Several fixes land for the BoxLang CLI runner:
The runner no longer misreads its own script path as a positional bundle argument.
KeyNotFoundException [url]no longer crashes every CLI run on BoxLang 1.17 and later. Theurlscope is not registered in CLI mode, and the runner now params it rather than referencing it directly.GetPageContextResponse()no longer errors when running BoxLang in Adobe compatibility mode.
Last updated
Was this helpful?