πŸ”· Core

coldbox-testing-base-classes

Use this skill to understand which ColdBox testing base class to extend for a given test type, configure test bundle annotations (appMapping, configMapping, unloadColdBox, loadColdBox, coldboxAppKey), set up the tests/ harness (Application.cfc, folder structure), or choose between integration testing (BaseTestCase), isolated handler testing (BaseHandlerTest), model unit testing (BaseModelTest), and interceptor unit testing (BaseInterceptorTest).

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

ColdBox Testing Base Classes

Class Hierarchy

testbox.system.BaseSpec
 └── coldbox.system.testing.BaseTestCase
      β”œβ”€β”€ coldbox.system.testing.BaseHandlerTest
      β”œβ”€β”€ coldbox.system.testing.BaseModelTest
      └── coldbox.system.testing.BaseInterceptorTest

Quick Decision Guide

I want to test…Extend
Handler actions using the full virtual app (execute / routes / interceptors)BaseTestCase
Handler CFC in complete isolation (no app load)BaseHandlerTest
A model/service/ORM entity in isolationBaseModelTest
An interceptor CFC in isolationBaseInterceptorTest
Any CFML/BX component with no ColdBox appBaseSpec (TestBox)

Bundle Annotations Reference

All ColdBox test bundles support these annotations:

AnnotationDefaultDescription
appMapping/Slash-notation path to the app root relative to the web root (e.g. /apps/blog)
configMapping{appMapping}/config/Coldbox.cfcDot-notation path to the config CFC
coldboxAppKeycbControllerApplication scope key where the controller is stored
loadColdBoxtrueBoot the virtual ColdBox app in beforeAll
unloadColdBoxtrueDestroy the virtual app in afterAll
class extends="coldbox.system.testing.BaseTestCase"
    appMapping="/apps/blog"
    configMapping="apps.blog.test.resources.Config"
    coldboxAppKey="cbController"
    unloadColdBox="false"
{}

Setting unloadColdBox="false" is a major performance win when multiple bundles share the same app.


Test Harness Structure

CommandBox generates this layout when you run coldbox create app:

tests/
β”œβ”€β”€ Application.cfc          ← harness Application.cfc
β”œβ”€β”€ resources/                ← fixtures, test data, test config
β”‚   └── Config.cfc
└── specs/
    β”œβ”€β”€ integration/          ← BaseTestCase bundles
    └── unit/
        β”œβ”€β”€ handlers/         ← BaseHandlerTest bundles
        β”œβ”€β”€ models/           ← BaseModelTest bundles
        └── interceptors/     ← BaseInterceptorTest bundles

tests/Application.cfc

class {
    this.name               = "ColdBoxTestingSuite"
    this.sessionManagement  = true
    this.sessionTimeout     = createTimeSpan( 0, 0, 15, 0 )
    this.applicationTimeout = createTimeSpan( 0, 0, 15, 0 )
    this.setClientCookies   = true

    // Map the test app root
    this.mappings[ "/root" ] = expandPath( "../" )

    function onRequestStart( string targetPage ) {
        // flush before each request
        if ( structKeyExists( url, "fwreinit" ) ) {
            if ( structKeyExists( server, "lucee" ) ) {
                pagePoolClear()
            }
        }
    }
}

BaseTestCase β€” Integration

Loads the full virtual ColdBox app. Use for end-to-end handler, route, and cross-layer tests.

class extends="coldbox.system.testing.BaseTestCase"
    appMapping="/root"
    unloadColdBox="false"
{
    function beforeAll() { super.beforeAll() }
    function afterAll()  { super.afterAll()  }

    function run() {
        describe( "My Integration Suite", () => {
            beforeEach( () => { setup() } )  // reset virtual request
            // specs...
        } )
    }
}

Key methods available:

MethodDescription
setup()Reset the virtual request (call in beforeEach)
execute( event, renderResults, ... )Simulate an event (GET-style)
get/post/put/patch/delete( route, params, headers, body )HTTP verb simulation
getInstance( name )Resolve a WireBox object
getController()Access the ColdBox controller
getWireBox()Access the WireBox injector
prepareMock( target )Prepare an object for MockBox

BaseHandlerTest β€” Isolated Handler Unit

Tests handler CFCs with no app load. You must mock every dependency.

class extends="coldbox.system.testing.BaseHandlerTest" handler="handlers.users" {

    function beforeAll() {
        super.setup()   // note: setup(), not beforeAll()
    }

    function run() {
        describe( "users handler β€” isolated", () => {
            it( "calls the service", () => {
                var mockSvc = createMock( "models.UserService" )
                handler.$property( propertyName = "userService", mock = mockSvc )
                mockSvc.$( "findAll" ).$results( [] )
                var event = execute( event = "users.index" )
                expect( mockSvc.$once( "findAll" ) ).toBeTrue()
            } )
        } )
    }
}

Available after super.setup():

  • variables.handler β€” the instantiated handler CFC
  • variables.handlerName β€” fully-qualified name
  • execute() β€” runs the handler action
  • prepareMock(), createMock() β€” MockBox helpers

BaseModelTest β€” Model / Service Unit

Tests model CFCs in isolation with pre-wired mock helpers. No app load.

class extends="coldbox.system.testing.BaseModelTest" model="models.UserService" {

    function beforeAll() {
        super.setup()
        model.init()   // call your own init if needed
    }

    function run() {
        describe( "UserService β€” isolated", () => {
            it( "computes something", () => {
                expect( model.compute() ).toBe( 42 )
            } )
        } )
    }
}

Available after super.setup():

VariableDescription
variables.modelInstantiated model under test
variables.mockLoggerMock LogBox logger
variables.mockLogBoxMock LogBox
variables.mockCacheBoxMock CacheBox
variables.mockWireBoxMock WireBox injector

BaseInterceptorTest β€” Interceptor Unit

Tests interceptor CFCs in isolation with pre-wired mock helpers. No app load.

class extends="coldbox.system.testing.BaseInterceptorTest"
    interceptor="interceptors.SecurityFirewall"
{
    function beforeAll() {
        super.setup()
    }

    function run() {
        describe( "SecurityFirewall β€” isolated", () => {
            it( "blocks unauthenticated requests", () => {
                interceptor.preProcess( mockRequestContext, {} )
                expect( mockRequestContext.$once( "noRender" ) ).toBeTrue()
            } )
        } )
    }
}

Available after super.setup():

VariableDescription
variables.interceptorInstantiated interceptor under test
variables.mockControllerMock ColdBox controller
variables.mockRequestServiceMock request service
variables.mockLoggerMock LogBox logger
variables.mockLogBoxMock LogBox
variables.mockFlashMock flash scope

Custom TestBox Matchers (ColdBox adds)

MatcherUsed onDescription
toHaveStatus( code )response objectAssert HTTP status code
toHaveInvalidData( field, msg )response objectAssert cbValidation field violation
toRedirectTo( event )event/request contextAssert relocation target

CommandBox Scaffolding

coldbox create integration-test handler=users actions=index,show,store
coldbox create unit-test name=UserService type=model
coldbox create unit-test name=SecurityFirewall type=interceptor