| name | testbox-bdd |
|---|---|
| description | Use this skill when writing BDD-style tests with TestBox using describe/it blocks, feature/story/scenario/given/when/then Gherkin-style suites, lifecycle hooks (beforeAll/afterAll/beforeEach/afterEach/aroundEach), focused specs (fit/fdescribe), skipping specs (xit/xdescribe/skip()), spec data binding, asyncAll parallel specs, nested suite trees, labels, or organizing tests around behavior descriptions. |
- Writing BDD-style test bundles for any ColdBox/BoxLang application
- Using
describe(),it(), or Gherkin aliases (feature/story/scenario/given/when/then) - Setting up lifecycle methods:
beforeAll,afterAll,beforeEach,afterEach,aroundEach - Focusing or skipping suites and specs
- Passing data into specs via data binding
- Running specs in parallel with
asyncAll
| Concept | BoxLang (.bx) preferred |
CFML (.cfc) compatible |
|---|---|---|
| Class declaration | class extends="testbox.system.BaseSpec" {} |
component extends="testbox.system.BaseSpec" {} |
| Arrow closures | () => {} |
function() {} |
| Lambda with args | ( currentSpec, data ) => {} |
function( currentSpec, data ) {} |
class extends="testbox.system.BaseSpec" {
// Global lifecycle — runs ONCE for the entire bundle
function beforeAll() {
variables.sut = getInstance( "models.UserService" )
}
function afterAll() {
// global teardown
}
function run( testResults, testBox ) {
describe( "UserService", () => {
// Suite lifecycle — runs around each it()
beforeEach( ( currentSpec ) => {
variables.mockRepo = createMock( "models.UserRepository" )
variables.sut.$property( propertyName = "userRepository", mock = mockRepo )
} )
afterEach( ( currentSpec ) => {
// per-spec teardown
} )
it( "creates a user and returns the saved entity", () => {
mockRepo.$( "save" ).$results( { id: 1, name: "Alice" } )
var result = sut.create( { name: "Alice", email: "alice@example.com" } )
expect( result ).toBeStruct()
expect( result.id ).toBeNumeric()
expect( result.name ).toBe( "Alice" )
} )
it( "throws ValidationException for missing email", () => {
expect( () => {
sut.create( { name: "Alice" } )
} ).toThrow( type: "ValidationException" )
} )
} )
}
}
Run once for the entire bundle — use for expensive one-time setup (boot DI container, seed DB, create JWT keys, etc.).
function beforeAll() {
ORMSessionClear()
structClear( request )
variables.jwt = getInstance( "JWTService@cbsecurity" )
variables.jwt.getSettings().jwt.tokenStorage.driver = "cachebox"
variables.securityService = getInstance( "SecurityService" )
}
function afterAll() {
variables.securityService.logout()
directoryDelete( "/tests/tmp", true )
}
Run around every it() in the containing describe(). In nested suites, TestBox walks down the tree for beforeEach and up for afterEach.
describe( "Outer", () => {
beforeEach( ( currentSpec, data ) => {
// runs first for all nested specs
} )
describe( "Inner", () => {
beforeEach( ( currentSpec, data ) => {
// runs second (after outer beforeEach)
} )
afterEach( ( currentSpec, data ) => {
// runs first on the way out (before outer afterEach)
} )
} )
afterEach( ( currentSpec, data ) => {
// runs last for all nested specs
} )
} )
Wraps each spec — useful for transaction rollbacks or resource acquisition/release patterns.
aroundEach( ( spec, suite ) => {
transaction {
spec.body() // run the spec inside the transaction
transactionRollback()
}
} )
describe() is aliased as: story(), feature(), scenario(), given(), when().
it() is aliased as then() (but then uses the argument name then instead of title).
feature( "Box Volume Calculator", () => {
scenario( "Calculate box volume", () => {
given( "a width of 20, height of 30, depth of 40", () => {
when( "I run the calculation", () => {
then( "the result is 24000", () => {
expect( boxCalc.volume( 20, 30, 40 ) ).toBe( 24000 )
} )
} )
} )
} )
} )
story( "As an author, I want to list all active users", () => {
given( "no options", () => {
then( "it returns all active system users", () => {
var event = this.get( "/cbapi/v1/authors" )
expect( event.getResponse() ).toHaveStatus( 200 )
expect( event.getResponse().getData() ).toBeArray().notToBeEmpty()
} )
} )
given( "isActive = false", () => {
then( "it returns inactive users", () => {
var event = this.get( "/cbapi/v1/authors?isActive=false" )
expect( event.getResponse() ).toHaveStatus( 200 )
expect( event.getResponse().getData() ).toBeArray().notToBeEmpty()
} )
} )
} )
Prefix with f to run only those suites/specs. All others are skipped.
fdescribe( "I only run", () => {
fit( "I am the only spec that runs", () => {
expect( true ).toBeTrue()
} )
it( "I am skipped because parent fdescribe runs all children", () => {
// still runs — parent focus runs all children
} )
} )
describe( "Normal suite", () => {
fit( "focused individual spec", () => {
expect( 1 ).toBe( 1 )
} )
it( "this one is skipped", () => { } )
} )
Focused prefixes work on all suite aliases:
fdescribe,fstory,ffeature,fscenario,fgiven,fwhen,fit,fthen.
Prefix with x or use the skip argument or call skip() inline.
xdescribe( "disabled suite", () => {
it( "never runs", () => { } )
} )
xit( "disabled spec", () => { } )
it( "engine-conditional skip", () => {
if ( !server.keyExists( "lucee" ) ) {
skip( "Only runs on Lucee" )
}
expect( luceeSpecificBehavior() ).toBeTrue()
} )
it( title: "conditional skip closure", body: () => {
expect( 1 ).toBe( 1 )
}, skip: () => {
return !structKeyExists( server, "railo" )
} )
Pass a data struct into it() to avoid closure-capture bugs in loops.
var filePaths = directoryList( "/tests/fixtures", false, "path", "*.json" )
for ( var filePath in filePaths ) {
it(
title: "#getFileFromPath( filePath )# is valid JSON",
data: { filePath: filePath },
body: ( data ) => {
var json = fileRead( data.filePath )
expect( json ).notToBeEmpty()
expect( isJSON( json ) ).toBeTrue( "File is not valid JSON: #data.filePath#" )
}
)
}
describe( "Payment processing", { labels: "integration,payments" }, () => {
describe( "Credit cards", { labels: "credit" }, () => {
it( "charges a card successfully", { labels: "happy-path" }, () => {
var result = paymentService.charge( fixtures.validCard, 99.99 )
expect( result.success ).toBeTrue()
expect( result.transactionId ).notToBeEmpty()
} )
it( "declines an expired card", () => {
expect( () => {
paymentService.charge( fixtures.expiredCard, 99.99 )
} ).toThrow( type: "PaymentDeclinedException" )
} )
} )
describe( "Refunds", () => {
it( "processes a full refund", () => {
var result = paymentService.refund( fixtures.completedTransaction )
expect( result.refunded ).toBeTrue()
} )
} )
} )
Run with labels: box testbox run labels=credit excludes=slow
describe( "Parallel cache reads", { asyncAll: true }, () => {
it( "reads product 1", () => {
var p = productService.getById( 1 )
expect( p.getId() ).toBe( 1 )
} )
it( "reads product 2", () => {
var p = productService.getById( 2 )
expect( p.getId() ).toBe( 2 )
} )
} )
Rules for asyncAll:
- All spec variables must be locally scoped (
var) or invariableswith no inter-spec mutation - Add CFThread
nameattributes or unique keys to avoid lock collisions - Reserve for IO-bound or concurrency-sensitive behavior; do not over-parallelize
| Function | Alias(es) | Description |
|---|---|---|
describe( title, body, [labels], [asyncAll], [skip] ) |
story, feature, scenario, given, when |
Define a test suite |
it( title, body, [labels], [skip], [data] ) |
then |
Define a spec / test case |
beforeAll( body ) |
— | Run once before all specs in the bundle |
afterAll( body ) |
— | Run once after all specs in the bundle |
beforeEach( body, [data] ) |
— | Run before each spec in the suite |
afterEach( body, [data] ) |
— | Run after each spec in the suite |
aroundEach( body ) |
— | Wrap each spec (transaction rollback pattern) |
expect( actual ) |
— | Start a fluent expectation chain |
expectAll( collection ) |
— | Assert every element in an array/struct |
skip( [message], [detail] ) |
— | Skip the current spec or suite inline |
addMatchers( matchers ) |
— | Register custom matchers |
getInstance( name ) |
— | Shortcut for WireBox getInstance() |
debug( var, [label] ) |
— | Output a variable to the debug panel |
# Create a new BDD spec
coldbox create bdd name=UserServiceSpec open=true
# Create with handler integration
coldbox create integration-test handler=users