๐Ÿ”ท Core trusted

testbox-mockbox

Use this skill when creating mocks, stubs, and spies in TestBox using MockBox: createMock(), createEmptyMock(), prepareMock(), stubbing methods with $(), chaining $args()/$results()/$throws(), verifying call counts with $once()/$never()/$times()/$atLeast()/$atMost(), reading call logs with $callLog(), injecting mock properties with $property(), simulating queries with querySim(), or spying on real methods with $spy().

$ npx skills add coldbox/skills/testbox/mockbox
$ coldbox ai skills install coldbox/skills/testbox/mockbox
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/testbox~mockbox

MockBox โ€” Mocking & Stubbing in TestBox

When to Use This Skill

  • Replacing real dependencies (DAOs, APIs, email services) with controlled test doubles
  • Stubbing method return values for different arguments
  • Verifying how many times a method was called and with what arguments
  • Making a method throw a specific exception
  • Spying on internal private methods of the object under test
  • Injecting mock property values directly into objects

Creating Test Doubles

// Full mock โ€” real class instantiated, all methods can be stubbed
variables.mockUserService = createMock( "models.UserService" )

// Full mock from an already-instantiated object
variables.mockUserService = createMock( object: new models.UserService() )

// Empty mock โ€” all methods wiped; you must stub every method used
variables.mockDAO = createEmptyMock( "models.UserDAO" )

// Partial mock โ€” decorate a real object; unstubbed methods execute normally
variables.spySecurity = prepareMock( new models.SecurityService() )
FactoryMethods kept?Use case
createMock()Yes (stubbable)Replace collaborator dependencies
createEmptyMock()No (all wiped)Mock an interface or abstract base
prepareMock()Yes (targeted stubs only)Spy on internal private methods

Stubbing Return Values โ€” $()

// Return a fixed value every call
mockDAO.$( "findById" ).$results( { id: 1, name: "Alice" } )

// Shorthand: inline returns
mockDAO.$( "findById", { id: 1, name: "Alice" } )

// Multiple sequential results (cycles on last after exhausting)
mockDAO.$( "getNextRecord" ).$results( { id: 1 }, { id: 2 }, { id: 3 } )
// call1 โ†’ {id:1},  call2 โ†’ {id:2},  call3+ โ†’ {id:3}

// Chain multiple stubs on one mock
mockService.$( "isFound", false ).$( "isDirty", false ).$( "isSaved", true )

// Dynamic result via callback (receives caller arguments as array)
mockCalc.$( "calculate", ( args ) => args[ 1 ] * 2 )

// Void method (returns nothing, just track calls)
mockEmailService.$( "send" )

Argument-Specific Stubs โ€” $args()

When the same method is called with different arguments and must return different values:

// Positional
mockConfig.$( "getKey" )
    .$args( "debugMode" ).$results( true )
    .$args( "outgoingMail" ).$results( "[email protected]" )

// Named
mockConfig.$( "getKey" )
    .$args( name: "debugMode" ).$results( true )
    .$args( name: "outgoingMail" ).$results( "[email protected]" )

// Usage
expect( mockConfig.getKey( "debugMode" ) ).toBeTrue()
expect( mockConfig.getKey( "outgoingMail" ) ).toBe( "[email protected]" )

Always follow $args() with $results().


Throwing Exceptions โ€” $throws()

// Chain approach
mockDAO.$( "delete" )
    .$args( id: 999 )
    .$throws( type: "NotFoundException", message: "Record 999 not found" )

// Inline approach
mockDAO.$(
    method:         "delete",
    throwException: true,
    throwType:      "NotFoundException",
    throwMessage:   "Record not found",
    throwDetail:    "id=999",
    throwErrorCode: "404"
)

// Confirm in spec
expect( () => service.delete( 999 ) ).toThrow( type: "NotFoundException" )

Injecting Properties โ€” $property()

Inject a mock directly into any scope of the SUT without needing a setter:

prepareMock( variables.sut )
    .$property(
        propertyName:  "userRepository",
        propertyScope: "variables",   // default
        mock:          mockUserRepository
    )

// Read it back
var injected = variables.sut.$getProperty( "userRepository" )

Query Simulation โ€” querySim()

Build query objects inline using pipe-delimited syntax:

var userQuery = querySim(
    "id, name, email
    1 | Alice Majano   | [email protected]
    2 | Bob Clapton    | [email protected]
    3 | Carol Degeneres| [email protected]"
)

mockDAO.$( "findAll" ).$results( userQuery )

var result = userService.listAll()
expect( result.recordCount ).toBe( 3 )
expect( result.name[ 1 ] ).toBe( "Alice Majano" )

Spying on Real Methods โ€” $spy()

The real implementation still executes; MockBox logs calls so you can verify them:

prepareMock( variables.sut )
variables.sut.$spy( "sendWelcomeEmail" )

userService.register( { name: "Alice", email: "[email protected]" } )

expect( variables.sut.$once( "sendWelcomeEmail" ) ).toBeTrue()

Verification Methods

// Exactly once
expect( mockEmailService.$once( "send" ) ).toBeTrue()

// Never called
expect( mockAuditService.$never( "logError" ) ).toBeTrue()

// Exactly N times
expect( mockDAO.$times( 3, "findById" ) ).toBeTrue()
expect( mockDAO.$verifyCallCount( 3, "findById" ) ).toBeTrue()   // alias

// At least N (>= N)
expect( mockDAO.$atLeast( 2, "findById" ) ).toBeTrue()

// At most N (<= N)
expect( mockDAO.$atMost( 5, "save" ) ).toBeTrue()

// Raw count
var total     = mockDAO.$count()            // all methods
var saveCount = mockDAO.$count( "save" )    // specific method

Inspecting Call Logs โ€” $callLog()

$callLog() returns a struct keyed by method name; each value is an array of argument structs.

mockSession.$( "setVar", callLogging: true )
mockSession.setVar( "Hello", "World" )
mockSession.setVar( "Name",  "Alice" )

var logs = mockSession.$callLog()
// logs.setVar is an array of 2 ordered argument structs
expect( logs.setVar ).toHaveLength( 2 )
expect( logs.setVar[ 1 ][ "1" ] ).toBe( "Hello" )   // positional key "1"
expect( logs.setVar[ 2 ][ "1" ] ).toBe( "Name" )

Resetting Between Specs

afterEach( () => {
    mockDAO.$reset()             // clears $count, $callLog, all stubs remain
    mockEmailService.$reset()
} )

Complete Integration Example

class extends="testbox.system.BaseSpec" {

    function beforeAll() {
        variables.mockUserDAO     = createEmptyMock( "models.UserDAO" )
        variables.mockEmailService = createEmptyMock( "models.EmailService" )

        variables.sut = prepareMock( new models.UserService() )
            .$property( propertyName: "userDAO",      mock: mockUserDAO )
            .$property( propertyName: "emailService", mock: mockEmailService )
    }

    function run() {

        describe( "UserService.register()", () => {

            beforeEach( () => {
                mockUserDAO.$reset()
                mockEmailService.$reset()
            } )

            it( "saves user and sends welcome email", () => {
                mockUserDAO.$( "save" ).$results( { id: 1, name: "Alice" } )
                mockEmailService.$( "sendWelcome" )

                var result = sut.register( { name: "Alice", email: "[email protected]" } )

                expect( result.id ).toBe( 1 )
                expect( mockUserDAO.$once( "save" ) ).toBeTrue()
                expect( mockEmailService.$once( "sendWelcome" ) ).toBeTrue()
            } )

            it( "does not send email when save fails", () => {
                mockUserDAO.$( "save" )
                    .$throws( type: "DatabaseException", message: "Duplicate entry" )

                expect( () => sut.register( { name: "Dup", email: "[email protected]" } ) )
                    .toThrow( type: "DatabaseException" )

                expect( mockEmailService.$never( "sendWelcome" ) ).toBeTrue()
            } )

            it( "calls save with the correct data arguments", () => {
                mockUserDAO.$( "save", callLogging: true ).$results( { id: 2 } )
                mockEmailService.$( "sendWelcome" )

                sut.register( { name: "Bob", email: "[email protected]" } )

                var log = mockUserDAO.$callLog()
                expect( log.save[ 1 ] ).toHaveKey( "name" )
                expect( log.save[ 1 ].name ).toBe( "Bob" )
            } )

        } )

    }

}

Quick Reference

MethodReturnsDescription
createMock( className\|object )mockFull mock, methods intact
createEmptyMock( className\|object )mockAll methods wiped
prepareMock( object )mockDecorate existing instance
mock.$( method, [returns] )mockStub a method
mock.$args( ...args )mockMatch specific call arguments
mock.$results( ...values )mockSet sequential return values
mock.$throws( type, message, detail )mockMake method throw
mock.$property( name, scope, mock )mockInject into any scope
mock.$getProperty( name, [scope] )anyRead internal property
mock.$spy( method )mockSpy on real method (real runs + log)
querySim( dsv )queryBuild query from pipe-delimited string
mock.$once( [method] )booleanCalled exactly once
mock.$never( [method] )booleanNever called
mock.$times( n, [method] )booleanCalled exactly N times
mock.$atLeast( n, [method] )booleanCalled >= N times
mock.$atMost( n, [method] )booleanCalled <= N times
mock.$count( [method] )numericCall count
mock.$callLog()structAll call argument logs
mock.$reset()voidReset counters and logs
mock.$debug()structDebugging info