testbox-assertions
Use this skill when using the TestBox $assert object for xUnit-style assertions: isTrue, isFalse, isTruthy, isFalsy, isEqual, isSameInstance, includes, includesAll, includesAny, includesNone, isEmpty, key, deepKey, instanceOf, throws, between, closeTo, lengthOf, match, null, typeOf, isJSON and others; grouping assertions with $assert.all() / assertAll() so every failure is reported at once; registering custom assertion functions with addAssertions(); or using BoxLang dynamic assertion methods (assertIsTrue, assertBetween, etc.).
TestBox Assertions โ $assert Reference
When to Use This Skill
- Writing xUnit-style assertions using the
$assertobject in test functions - Using the full
testbox.system.AssertionAPI for validation - Reporting every failure in a group at once with
$assert.all()/assertAll() - Registering inline or class-based custom assertions with
addAssertions() - Using BoxLang dynamic
assertXxx()method variants
The $assert Object
Every TestBox bundle receives $assert โ an instance of testbox.system.Assertion. It is available in both xUnit test functions and BDD it() blocks.
function testUserCreation() {
var user = userService.create( { name: "Alice", email: "[email protected]" } )
$assert.isNotNull( user )
$assert.isEqual( "Alice", user.name )
$assert.typeOf( "struct", user )
}
Complete Assertion Reference
Boolean
$assert.isTrue( actual, [message] )
$assert.isFalse( actual, [message] )
// TestBox 7.1+ โ loose truthiness.
// Falsy = false, 0, "" (empty string) and null. Everything else is truthy.
$assert.isTruthy( actual, [message] )
$assert.isFalsy( actual, [message] )
Equality
$assert.isEqual( expected, actual, [message] ) // case-insensitive
$assert.isEqualWithCase( expected, actual, [message] )
$assert.isNotEqual( expected, actual, [message] )
Identity
// Same object, not merely an equal one โ useful for proving WireBox scopes
$assert.isSameInstance( expected, actual, [message] )
$assert.isNotSameInstance( expected, actual, [message] )
Null
$assert.null( actual, [message] )
$assert.notNull( actual, [message] )
Emptiness
$assert.isEmpty( target, [message] ) // array, struct, string, query
$assert.isNotEmpty( target, [message] )
Size / Length
$assert.lengthOf( target, length, [message] )
$assert.notLengthOf( target, length, [message] )
Key Existence (Structs)
$assert.key( target, key, [message] )
$assert.notKey( target, key, [message] )
$assert.deepKey( target, key, [message] ) // recursive search
$assert.notDeepKey( target, key, [message] )
Inclusion (Strings / Arrays)
$assert.includes( target, needle, [message] ) // case-insensitive
$assert.includesWithCase( target, needle, [message] )
$assert.notIncludes( target, needle, [message] )
$assert.notIncludesWithCase( target, needle, [message] )
// TestBox 7.1+ โ multi-needle forms. `needles` is an ARRAY; matching is case-insensitive.
$assert.includesAll( target, needles, [message] ) // every needle must be found
$assert.includesAny( target, needles, [message] ) // at least one needle must be found
$assert.includesNone( target, needles, [message] ) // no needle may be found
// Examples
$assert.includesAll( response.body, [ "id", "name", "email" ] )
$assert.includesAny( logOutput, [ "WARN", "ERROR" ] )
$assert.includesNone( serializeJSON( memento ), [ "password", "salt" ] )
Type Checks
$assert.typeOf( type, actual, [message] ) // uses isValid() under the hood
$assert.notTypeOf( type, actual, [message] )
$assert.instanceOf( actual, typeName, [message] )
$assert.notInstanceOf( actual, typeName, [message] )
Common type values: array, struct, component, numeric, boolean, date, string, uuid, email, url, query
Numeric Comparisons
$assert.isGT( actual, target, [message] )
$assert.isGTE( actual, target, [message] )
$assert.isLT( actual, target, [message] )
$assert.isLTE( actual, target, [message] )
$assert.between( actual, min, max, [message] )
$assert.closeTo( expected, actual, delta, [datePart], [message] )
String / Regex
$assert.match( actual, regex, [message] ) // case-insensitive
$assert.matchWithCase( actual, regex, [message] )
$assert.notMatch( actual, regex, [message] )
Exceptions
// Assert a closure THROWS
$assert.throws( target, [type], [regex], [message] )
// target must be a closure/function reference
$assert.throws( () => service.delete( 999 ), "NotFoundException" )
$assert.throws( () => service.delete( 999 ), "NotFoundException", "999" )
// Assert a closure DOES NOT throw
$assert.notThrows( target, [type], [regex], [message] )
$assert.notThrows( () => service.safeOperation() )
Force Pass/Fail/Skip
$assert.fail( [message] ) // forcibly fail the test
$assert.skip( message, [detail] ) // skip current test with a message
Generic Expression Assert
$assert.assert( expression, [message] )
// example:
$assert.assert( user.age >= 18, "User must be an adult" )
Grouped Assertions โ $assert.all() / assertAll()
TestBox 7.1+. Works on every engine.
A normal test stops at the first failed assertion, so you fix one thing, re-run, and discover
the next. $assert.all() runs every closure you hand it, collects the failures, and reports them
together in one aggregated TestBox.AssertionFailed โ message says how many failed, detail
numbers each one.
$assert.all( executables, [heading] )
executablesโ an array of closures, each containing one or more assertionsheadingโ optional text prefixed to the aggregated failure message
function testUserShape() {
var user = userService.create( { name: "Alice", email: "[email protected]" } )
$assert.all( [
() => $assert.key( user, "id" ),
() => $assert.isEqual( "Alice", user.name ),
() => $assert.isEqual( "[email protected]", user.email ),
() => $assert.typeOf( "date", user.createdAt )
], "User shape" )
// Failure reads: User shape โ 2 assertion(s) failed, with both listed in the detail
}
assertAll() is the spec-level shortcut for the same thing, and reads better inside BDD blocks.
It takes the identical arguments and delegates straight to $assert.all():
it( "returns a fully-populated response", () => {
var res = api.get( "/users/1" )
assertAll( [
() => expect( res.status ).toBe( 200 ),
() => expect( res.body ).toHaveKey( "id" ),
() => expect( res.body ).toHaveKey( "name" ),
() => expect( res.body ).notToHaveKey( "passwordHash" )
], "GET /users/1" )
} )
Closures may mix
$assertandexpect()freely โ both raiseTestBox.AssertionFailed, which is what the group collects. A closure that throws anything else is not caught, so a genuine runtime error still surfaces immediately rather than being folded into the summary.
Reach for it when several assertions describe one outcome (a response shape, an entity's
fields, a config block). Keep separate it() blocks for genuinely separate behaviours.
BoxLang Dynamic Assertion Methods
When running tests in BoxLang, you can call any $assert method as a top-level function by prefixing it with assert:
// Standard $assert style
$assert.isTrue( myBool )
$assert.isEqual( expected, actual )
$assert.between( value, 1, 100 )
// BoxLang dynamic method style (identical behaviour)
assertIsTrue( myBool )
assertIsEqual( expected, actual )
assertBetween( value, 1, 100 )
assertThrows( () => badCall(), "MyException" )
assertCloseTo( 3.14, 3.14159, 0.01 )
Common Assertion Patterns
// Struct shape
$assert.isNotEmpty( responseData )
$assert.key( responseData, "id" )
$assert.key( responseData, "name" )
$assert.typeOf( "numeric", responseData.id )
// Array contents
$assert.typeOf( "array", results )
$assert.isNotEmpty( results )
$assert.lengthOf( results, 3 )
$assert.includes( results, "expectedValue" )
// Exception handling
$assert.throws(
() => userService.findById( -1 ),
"ValidationException",
"-1"
)
// Approximate date equality
$assert.closeTo(
expected: now(),
actual: user.createdAt,
delta: 2,
datePart: "s"
)
Custom Assertions
Inline Registration
Register in beforeTests() (xUnit) or beforeAll() (BDD) to keep assertions clean across specs:
function beforeTests() {
addAssertions( {
isValidEmail: function( actual ) {
return isValid( "email", actual )
? true
: fail( "[#actual#] is not a valid email address" )
},
isUUID: function( actual ) {
return isValid( "uuid", actual )
? true
: fail( "[#actual#] is not a UUID" )
},
hasNoErrors: function( actual ) {
return ( actual.keyExists( "errors" ) && !actual.errors.isEmpty() )
? fail( "Response has errors: #serializeJSON( actual.errors )#" )
: true
}
} )
}
// Usage
function testEmailValidation() {
$assert.isValidEmail( "[email protected]" ) // passes
$assert.isValidEmail( "not-an-email" ) // fails
$assert.isUUID( createUUID() )
}
Class-Based Assertion Library
For shared, reusable assertions across multiple test bundles:
// tests/helpers/AppAssertions.cfc
component {
function assertIsActiveUser( expected, actual ) {
return ( actual.keyExists( "isActive" ) && actual.isActive )
? true
: fail( "User [#actual.name ?: 'unknown'#] is not active" )
}
function assertHasPermission( expected, actual ) {
return ( actual.permissions.findNoCase( expected ) > 0 )
? true
: fail( "User does not have permission [#expected#]" )
}
function assertResponseSuccess( expected, actual ) {
return actual.success
? true
: fail( "Response was not successful. Message: #actual.message ?: ''#" )
}
}
function beforeTests() {
// Single class
addAssertions( "tests.helpers.AppAssertions" )
// Multiple classes
addAssertions( "tests.helpers.AppAssertions,tests.helpers.SecurityAssertions" )
// or array:
addAssertions( [ "tests.helpers.AppAssertions", "tests.helpers.SecurityAssertions" ] )
}
function testAdminUser() {
var admin = userService.findById( 1 )
$assert.isActiveUser( admin )
$assert.hasPermission( "ADMIN", admin )
}
$assert vs expect() Guide
Use $assert when:
- Writing xUnit-style test functions
- You prefer imperative
isEqual( a, b )syntax - You need
assert( expression )for arbitrary boolean checks
Use expect() when:
- Writing BDD specs
- You want fluent chaining:
expect( x ).toBe( y ).toHaveKey( "z" ) - You want the collection starters (
expectAll,expectAny,expectSome,expectNone) - You want
withContext()to label failure messages - You want custom matchers with
notnegation - You need the BoxLang-only Set, Range or Data Navigator matchers
Either style can be grouped with $assert.all() / assertAll() โ see the
testbox-expectations skill for the matcher side.
Both are available in the same bundle โ mix freely.