Handler Testing in ColdBox
When to Use This Skill
- Writing tests for ColdBox handlers/controllers
- Asserting what view, layout, or data a handler returns
- Verifying redirect/relocation behavior after form submissions
- Testing RESTful handlers that call
renderData() - Checking HTTP status codes set by handlers
- Unit-testing a handler in full isolation with
BaseHandlerTest
Language Mode Reference
| Concept | BoxLang (.bx) preferred |
CFML (.cfc) compatible |
|---|---|---|
| Class declaration | class extends="..." {} |
component extends="..." {} |
| Closures | () => {} |
function() {} |
Testing Class Choices
| Class | Purpose |
|---|---|
coldbox.system.testing.BaseTestCase |
Integration — loads the full virtual ColdBox app |
coldbox.system.testing.BaseHandlerTest |
Isolated unit — tests the handler CFC with no app load |
Use BaseTestCase for most handler tests. Use BaseHandlerTest only when you want full isolation.
Integration Handler Test (BaseTestCase)
class extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
function beforeAll() {
super.beforeAll()
}
function afterAll() {
super.afterAll()
}
function run() {
describe( "Main Handler", () => {
beforeEach( () => {
// CRITICAL: fresh virtual request per spec
setup()
} )
it( "renders the homepage", () => {
var event = execute( event = "main.index", renderResults = true )
expect( event.getPrivateValue( "welcomeMessage" ) ).toBe( "Welcome to ColdBox!" )
expect( event.getCurrentView() ).toBe( "main/index" )
} )
it( "places users in prc", () => {
var event = execute( event = "users.index", renderResults = true )
expect( event.getPrivateValue( "users" ) ).toBeArray()
} )
it( "stores a user and redirects", () => {
var event = execute(
event = "users.store",
renderResults = true,
eventArguments = { name: "Alice", email: "alice@example.com" }
)
expect( event.getValue( "relocate_event", "" ) ).toBe( "users.index" )
} )
} )
}
}
The execute() Method
execute() simulates a ColdBox event and returns the request context. It is limited to GET-style simulation. For POST/PUT/DELETE use the HTTP method helpers.
| Argument | Type | Default | Description |
|---|---|---|---|
event |
string | — | Event to execute e.g. "users.index" |
route |
string | — | Route URL e.g. "/users" — also exercises URL mappings |
queryString |
string | "" |
Query string appended to the simulated request |
private |
boolean | false |
Execute as a private event |
prePostExempt |
boolean | false |
Skip pre/post interceptors |
eventArguments |
struct | {} |
Arguments passed directly into the handler action |
renderResults |
boolean | false |
Render output and store in cbox_rendered_content |
withExceptionHandling |
boolean | false |
Route errors through ColdBox exception handling |
domain |
string | cgi.server_name |
Simulate a specific domain |
Asserting Results
Handler Return Value
// handler
function list( event, rc, prc ) {
return "Hola Luis"
}
// spec
var event = execute( event = "main.list", renderResults = true )
expect( event.getHandlerResults() ).toBe( "Hola Luis" )
// also available as rc value
expect( event.getValue( "cbox_handler_results" ) ).toBe( "Hola Luis" )
View Selection
var event = execute( event = "users.index", renderResults = true )
expect( event.getCurrentView() ).toBe( "users/index" )
Rendered Content
var event = execute( event = "main.data", renderResults = true )
expect( event.getRenderedContent() ).toBeJSON()
renderData() — REST handlers
// handler
function data( event, rc, prc ) {
event.renderData( data = myService.getData(), type = "json" )
}
// spec
var event = execute( event = "main.data", renderResults = true )
expect( event.getRenderData().type ).toBe( "json" )
expect( event.getPrivateValue( "cbox_renderdata" ).type ).toBe( "json" )
HTTP Status Code
var event = execute( event = "api.users.show", renderResults = true )
expect( event.getStatusCode() ).toBe( 200 )
// ColdBox matcher shorthand
expect( event.getResponse() ).toHaveStatus( 200 )
prc / rc Values
// private collection (set by handler via prc.x = ...)
var user = event.getPrivateValue( "user" )
expect( user ).toHaveKey( "id" )
expect( user.email ).toBe( "alice@example.com" )
// public collection (from rc)
var name = event.getValue( "name" )
Mocking Relocations
ColdBox automatically stubs relocate() during tests. Relocation arguments are saved as relocate_* keys in the rc.
it( "redirects to users.index after save", () => {
var event = execute(
event = "users.store",
eventArguments = { name: "Bob", email: "bob@example.com" }
)
expect( event.getValue( "relocate_event", "" ) ).toBe( "users.index" )
} )
it( "redirects with status 301", () => {
var event = execute( event = "legacy.redirect" )
expect( event.getValue( "relocate_statusCode", "" ) ).toBe( "301" )
} )
The available relocate_* keys:
| Key | Description |
|---|---|
relocate_event |
Target ColdBox event |
relocate_URL |
Target URL |
relocate_URI |
Target URI |
relocate_queryString |
Query string |
relocate_statusCode |
HTTP redirect status code |
Or use the ColdBox TestBox matcher:
expect( event ).toRedirectTo( "main.index" )
Lifecycle and setup()
function beforeAll() {
super.beforeAll()
// one-time app-level setup
}
function afterAll() {
super.afterAll()
}
function run() {
describe( "Users Handler", () => {
beforeEach( () => {
setup() // fresh virtual request per spec — never skip this
} )
// specs...
} )
}
Important: If you do not call
super.beforeAll()/super.afterAll(), the virtual app will not load/unload correctly. Always funnel the super call.
Performance: Keep ColdBox Loaded Across Bundles
By default the virtual app is destroyed after each bundle. To share it across bundles for faster runs:
class extends="coldbox.system.testing.BaseTestCase"
appMapping="/root"
unloadColdBox="false"
{
this.unloadColdBox = false
// ...
}
Test Annotations
| Annotation | Default | Description |
|---|---|---|
appMapping |
/ |
Slash-notation path to the ColdBox app root |
configMapping |
{appMapping}/config/Coldbox.cfc |
Dot-notation path to config CFC |
coldboxAppKey |
cbController |
Application scope key for the controller |
loadColdBox |
true |
Whether to load the virtual app |
unloadColdBox |
true |
Whether to unload the virtual app after specs |
class extends="coldbox.system.testing.BaseTestCase"
appMapping="/apps/MyApp"
configMapping="apps.MyApp.test.resources.Config"
{}
Injecting Mock Services
beforeEach( () => {
setup()
variables.mockUserService = createMock( "models.UserService" )
prepareMock( getInstance( "users@handlers" ) )
.$property( propertyName = "userService", mock = mockUserService )
} )
it( "calls userService.findAll", () => {
mockUserService.$( "findAll" ).$results( [ { id: 1, name: "Alice" } ] )
var event = execute( event = "users.index", renderResults = true )
expect( mockUserService.$once( "findAll" ) ).toBeTrue()
expect( event.getPrivateValue( "users" ) ).toHaveLength( 1 )
} )
Isolated Handler Unit Test (BaseHandlerTest)
BaseHandlerTest tests the handler CFC in complete isolation — no virtual ColdBox app. Mock all dependencies manually.
class extends="coldbox.system.testing.BaseHandlerTest" handler="handlers.users" {
function beforeAll() {
super.setup()
mockUserService = createMock( "models.UserService" )
handler.$property( propertyName = "userService", mock = mockUserService )
}
function run() {
describe( "users handler — isolated", () => {
it( "index places users in prc", () => {
mockUserService.$( "findAll" ).$results( [ { id: 1 } ] )
var event = execute( event = "users.index" )
expect( event.getPrivateValue( "users" ) ).toHaveLength( 1 )
} )
} )
}
}
Integration tests load the full ColdBox app; isolated handler tests (
BaseHandlerTest) do not. In isolated tests you must mock every dependency the handler uses.
CommandBox Scaffolding
coldbox create integration-test handler=users actions=index,show,store --open
Key Methods Quick Reference
| Method | Description |
|---|---|
execute( event, renderResults, eventArguments ) |
Execute a ColdBox event |
setup() |
Reset the virtual request — call in beforeEach |
event.getCurrentView() |
View name chosen by the handler |
event.getRenderedContent() |
Rendered output string |
event.getHandlerResults() |
Value explicitly returned by the handler action |
event.getRenderData() |
Struct from renderData() call |
event.getPrivateValue( key ) |
Read from prc |
event.getValue( key ) |
Read from rc |
event.getStatusCode() |
HTTP status code |
event.getResponse() |
ColdBox response object (supports .toHaveStatus()) |
prepareMock( handler ) |
Prepare handler for property injection |
getInstance( name ) |
Get a WireBox-managed object |