๐Ÿ”ท Core

coldbox-testing-handler

Use this skill when testing ColdBox event handlers with execute(), asserting rc/prc collections, verifying view selection and rendered output, mocking relocations, testing renderData() and getHandlerResults(), setting HTTP methods and headers, injecting mocks into handlers, or using BaseHandlerTest for isolated handler unit tests.

$ npx skills add coldbox/skills/coldbox/testing-handler
$ coldbox ai skills install coldbox/skills/coldbox/testing-handler
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/coldbox~testing-handler

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

ConceptBoxLang (.bx) preferredCFML (.cfc) compatible
Class declarationclass extends="..." {}component extends="..." {}
Closures() => {}function() {}

Testing Class Choices

ClassPurpose
coldbox.system.testing.BaseTestCaseIntegration โ€” loads the full virtual ColdBox app
coldbox.system.testing.BaseHandlerTestIsolated 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: "[email protected]" }
                )
                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.

ArgumentTypeDefaultDescription
eventstringโ€”Event to execute e.g. "users.index"
routestringโ€”Route URL e.g. "/users" โ€” also exercises URL mappings
queryStringstring""Query string appended to the simulated request
privatebooleanfalseExecute as a private event
prePostExemptbooleanfalseSkip pre/post interceptors
eventArgumentsstruct{}Arguments passed directly into the handler action
renderResultsbooleanfalseRender output and store in cbox_rendered_content
withExceptionHandlingbooleanfalseRoute errors through ColdBox exception handling
domainstringcgi.server_nameSimulate 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( "[email protected]" )

// 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: "[email protected]" }
    )
    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:

KeyDescription
relocate_eventTarget ColdBox event
relocate_URLTarget URL
relocate_URITarget URI
relocate_queryStringQuery string
relocate_statusCodeHTTP 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

AnnotationDefaultDescription
appMapping/Slash-notation path to the ColdBox app root
configMapping{appMapping}/config/Coldbox.cfcDot-notation path to config CFC
coldboxAppKeycbControllerApplication scope key for the controller
loadColdBoxtrueWhether to load the virtual app
unloadColdBoxtrueWhether 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

MethodDescription
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