BoxLang ๐Ÿš€ A New JVM Dynamic Language Learn More...

TestBox

v6.4.0+17 Testing

Total Downloads Latest Stable Version Apache2 License

Copyright Since 2005 TestBox by Luis Majano and Ortus Solutions, Corp
www.testbox.run | www.ortussolutions.com

TestBox - BDD/TDD Testing Framework for BoxLang & CFML

Professional BDD (Behavior-Driven Development) and TDD (Test-Driven Development) testing framework for BoxLang and CFML applications. TestBox provides a comprehensive testing ecosystem with integrated MockBox mocking capabilities, multiple output formats, and both CLI and web-based test runners.

TestBox v6 Now Available: Read the announcement ยป

๐ŸŒŸ Features

  • Dual Testing Approaches: Full support for both BDD (describe(), it()) and xUnit (setup(), test*()) styles
  • BoxLang First: Native support for BoxLang with dedicated CLI runner, plus full CFML compatibility
  • Integrated Mocking: Built-in MockBox framework with spies, stubs, and verification capabilities
  • Rich Data Generation: CBMockData integration for realistic test data (names, addresses, dates)
  • Multiple Reporters: 15+ output formats including HTML, JSON, XML, TAP, JUnit, and Console
  • CLI & Web Runners: Execute tests via CommandBox CLI or web-based interfaces
  • File Watching: Automatic test re-execution on code changes during development
  • Code Coverage: Built-in coverage analysis and reporting
  • Flexible Discovery: Automatic test bundle detection with customizable patterns
  • Thread-Safe: Concurrent test execution with proper isolation

๐Ÿ’ป Requirements

  • BoxLang: 1.0+
  • Lucee: 5.0+
  • Adobe ColdFusion: 2021+

โšก Quick Start

1. Installation

# Install TestBox and CLI tools via CommandBox
box install testbox testbox-cli

# Or install bleeding edge
box install testbox@be testbox-cli

2. Create Your First Test

// tests/specs/UserServiceTest.cfc
class extends="testbox.system.BaseSpec" {

    function run() {
        describe( "UserService", () => {
            beforeEach( () => {
                userService = new models.UserService();
            } );

            it( "should create a new user", () => {
                var user = userService.createUser( "[email protected]", "John Doe" );
                expect( user.getEmail() ).toBe( "[email protected]" );
                expect( user.getName() ).toBe( "John Doe" );
            } );
        } );
    }
}

๐ŸŽญ MockBox Integration & Test Data

Advanced Mocking with MockBox

class extends="testbox.system.BaseSpec" {

    function run() {
        describe( "Payment Service", () => {
            beforeEach( () => {
                // Create mocks and spies
                mockGateway = createMock( "services.PaymentGateway" );
                mockLogger = createEmptyMock( "cblogger.models.Logger" );

                // Setup mock behavior
                mockGateway.$( "processPayment" ).$results( {
                    success: true,
                    transactionId: "TXN-12345"
                } );

                paymentService = new models.PaymentService(
                    gateway = mockGateway,
                    logger = mockLogger
                );
            } );

            it( "should process payment and log success", () => {
                var result = paymentService.charge( 100.00, "USD" );

                // Verify method calls
                expect( mockGateway.$times( 1, "processPayment" ) ).toBeTrue();
                expect( mockLogger.$times( 1, "info" ) ).toBeTrue();

                // Verify results
                expect( result.success ).toBeTrue();
                expect( result.transactionId ).toBe( "TXN-12345" );
            } );
        } );
    }
}### Realistic Test Data with CBMockData

```javascript
class extends="testbox.system.BaseSpec" {

    property name="mockData" inject="MockData@cbMockData";

    function run() {
        describe("User Profile Tests", () => {
            it("should handle various user data scenarios", () => {
                // Generate realistic test data
                var testUsers = [];

                for (var i = 1; i <= 10; i++) {
                    arrayAppend(testUsers, {
                        firstName: mockData.fname(),
                        lastName: mockData.lname(),
                        email: mockData.email(),
                        age: mockData.age(),
                        address: {
                            street: mockData.streetaddress(),
                            city: mockData.city(),
                            state: mockData.state(),
                            zipCode: mockData.zipcode()
                        },
                        registrationDate: mockData.datetime()
                    });
                }

                // Test with realistic data
                for (var user in testUsers) {
                    var profile = userService.createProfile(user);
                    expect(profile.isValid()).toBeTrue();
                    expect(profile.getEmail()).toMatch("^[\w\.-]+@[\w\.-]+\.[A-Za-z]{2,}$");
                }
            });
        });
    }
}

3. Run Your Tests

# Via CommandBox CLI
box testbox run

# Via BoxLang CLI Runner (fastest execution)
./testbox/run                           # Run default tests.specs
./testbox/run --directory=my.tests      # Specific directory
./testbox/run --bundles=my.bundle       # Specific bundles
./testbox/run --reporter=json           # Custom reporter

# Via web browser
# Navigate to: http://localhost/testbox/system/runners/HTMLRunner.cfm

๐Ÿงช Testing Approaches

BDD Style (Behavior-Driven Development)

```javascript
class extends="testbox.system.BaseSpec" {

    function run() {
        describe( "User Registration", () => {
            beforeEach(() => {
                userService = createMock("models.UserService");
                variables.sut = new handlers.Users();
            });

            describe("When registering a new user", () => {
                it("should validate email format", () => {
                    expect(() => {
                        userService.register("invalid-email", "password");
                    }).toThrow("ValidationException");
                });

                it("should create user with valid data", () => {
                    var result = userService.register("[email protected]", "securePass");
                    expect(result.success).toBeTrue();
                    expect(result.user.email).toBe("[email protected]");
                });
            });
        });
    }
}

xUnit Style (Traditional Unit Testing)

```javascript
class extends="testbox.system.BaseSpec" {

    function setup() {
        // Runs before each test
        userService = new models.UserService();
        testData = {
            email: "[email protected]",
            name: "Test User"
        };
    }

    function testUserCreation() {
        var user = userService.createUser( testData.email, testData.name );
        $assert.isEqual( testData.email, user.getEmail() );
        $assert.isEqual( testData.name, user.getName() );
    }

    function testEmailValidation() {
        $assert.throws( () => {
            userService.createUser( "invalid-email", "Test User" );
        }, "ValidationException" );
    }

    function tearDown() {
        // Cleanup after each test
        structDelete( variables, "userService" );
        structDelete( variables, "testData" );
}

โš™๏ธ Configuration & CLI Usage

CLI Test Execution

# Run all tests with default settings
box testbox run

# Run specific test bundles
box testbox run bundles=tests.specs.UserServiceTest

# Run tests with custom reporter
box testbox run reporter=json

# Run tests with labels ( focused testing )
box testbox run labels=unit --excludes=integration

# Watch mode for continuous testing
box testbox watch

# Generate test templates
box testbox create bdd MyNewTest
box testbox create unit MyNewTest

Application Setup

Create an Application.cfc in your test directory:

class {
    this.name = "MyApp-Tests-" & hash( getCurrentTemplatePath() );
    this.sessionManagement = true;
    this.sessionTimeout = createTimeSpan(0, 0, 15, 0);
    this.applicationTimeout = createTimeSpan(0, 0, 15, 0);
    this.setClientCookies = true;

    // TestBox mappings
    this.mappings[ "/testbox" ] = expandPath( "/testbox" );
    this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() );
    this.mappings[ "/models" ] = expandPath( "/models" );

    // Module mappings
    this.mappings[ "/cbstreams" ] = expandPath( "/testbox/system/modules/cbstreams" );
    this.mappings[ "/cbMockData" ] = expandPath( "/testbox/system/modules/cbMockData" );
    this.mappings[ "/globber" ] = expandPath( "/testbox/system/modules/globber" );    function onApplicationStart() {
        application.wirebox = new coldbox.system.ioc.Injector();
        return true;
    }

    function onRequestStart() {
        // Reset ORM on every request for clean testing
        if (structKeyExists(url, "fwreinit")) {
            if (structKeyExists(server, "lucee")) {
                pagePoolClear();
            }
            ormReload();
        }
    }
}

Web Runner Configuration

Create tests/runner.cfm for web-based test execution:

<cfsetting showDebugOutput="false">
<!DOCTYPE html>
<html>
<head>
    <title>My Application Test Suite</title>
</head>
<body>
    <cfscript>
        // Create TestBox instance
        testbox = new testbox.system.TestBox(
            options = {
                // Test bundle directories
                bundles = [
                    "tests.specs"
                ],
                // Directories to include/exclude
                directory = {
                    mapping = "tests.specs",
                    recurse = true
                },
                // Test labels
                labels = url.labels ?: "",
                excludes = url.excludes ?: "",
                // Reporter
                reporter = url.reporter ?: "simple",
                // Coverage settings
                coverage = {
                    enabled = true,
                    pathToCapture = expandPath("/models"),
                    whitelist = "*.cfc",
                    blacklist = "*Test*.cfc"
                }
            }
        );

        // Run tests and output results
        writeOutput(testbox.run());
    </cfscript>
</body>
</html>

๐Ÿ“„ License

Apache License, Version 2.0.

๐Ÿ“‹ Versioning

TestBox is maintained under the Semantic Versioning guidelines as much as possible.

Releases will be numbered with the following format:

<major>.<minor>.<patch>

And constructed with the following guidelines:

  • Breaking backward compatibility bumps the major (and resets the minor and patch)
  • New additions without breaking backward compatibility bumps the minor (and resets the patch)
  • Bug fixes and misc changes bumps the patch

๐Ÿ“š Documentation & Resources

Source Code

Bug Tracking

Community & Support

Official Documentation

Community and Support

Join us in our Ortus Community and become a valuable member of this project TestBox BDD. We are looking forward to hearing from you!!

Official Sites

๐Ÿ“„ License

Apache License, Version 2.0. See LICENSE file for details.


About Ortus Solutions

TestBox is a professional open-source project by Ortus Solutions.


Copyright Since 2005 TestBox by Luis Majano and Ortus Solutions, Corp

www.testbox.run | www.ortussolutions.com


โœ๏ธ HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.

"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; And patience, experience; and experience, hope: And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the Holy Ghost which is given unto us." Romans 5:5

๐Ÿž THE DAILY BREAD

"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" John 14:1-12

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


Unreleased

6.4.0 - 2025-09-18

New Features

Bugs

  • TESTBOX-421 `equalize` should use the Java `equals` check at the end of the function instead of `false`

Tasks

  • TESTBOX-424 Add Dumb Adobe jvm flags to allow for dynamic arguments

6.3.2 - 2025-04-29

6.3.1 - 2025-04-01

Fixed

  • Update the run runners so they use the calculated location paths.

6.3.1 - 2025-04-01

Fixed

  • Fixed a typo in BaseReporter

6.3.0 - 2025-03-31

Bugs

  • TESTBOX-418 initArgs.bundles can be an array or simple value, consolidate it in boxlang runner
  • TESTBOX-420 Reporter options not being passed correctly

Improvements

  • TESTBOX-417 BoxLang only usage improvements
  • TESTBOX-419 Console reporter now includes colors and abiliy to execute via boxlang

6.3.0 - 2025-02-25

6.2.1 - 2025-02-06

6.2.0 - 2025-01-31

6.1.0 - 2025-01-28

New Features

  • TESTBOX-412 Updated to use cbMockData now instead of MockDataCFC

Improvements

Bugs

  • TESTBOX-408 Allow toHaveKey to support struct like objects
  • TESTBOX-410 Error when using the url.excludes with the HTML runner
  • TESTBOX-411 fix missing `cfloop` on test browser

6.0.1 - 2024-12-05

6.0.0 - 2024-09-27

New Features

  • TESTBOX-391 MockBox converted to script
  • TESTBOX-392 BoxLang classes support
  • TESTBOX-393 New environment helpers to do skip detections or anything you see fit: isAdobe, isLucee, isBoxLang, isWindows, isMac, isLinux
  • TESTBOX-394 new test(), xtest(), ftest() alias for more natuarl testing
  • TESTBOX-397 debug() get's two new arguments: label and showUDFs
  • TESTBOX-398 DisplayName on a bundle now shows up in the reports
  • TESTBOX-399 xUnit new annotation for @DisplayName so it can show instead of the function name
  • TESTBOX-401 BoxLang CLI mode and Runner
  • TESTBOX-402 New matcher: toHaveKeyWithCase()
  • TESTBOX-403 Assertions: key() and notKey() now have a CaseSensitive boolean argument

Improvements

  • TESTBOX-289 showUDFs = false option with debug()
  • TESTBOX-331 TextReporter doesn't correctly support testBundles URL param
  • TESTBOX-395 adding missing focused argument to spec methods
  • TESTBOX-396 Generating a repeatable id for specs to track them better in future UIs

Bugs

  • TESTBOX-123 If test spec descriptor contains a comma, it can not be drilled down to run that one spec directly
  • TESTBOX-338 describe handler in non-called test classes being executed

Tasks

  • TESTBOX-400 Drop Adobe 2018 support

$ box install testbox

No collaborators yet.
     
5.00 / 2
  • {{ getFullDate("2014-05-29T16:22:57Z") }}
  • {{ getFullDate("2025-09-18T16:08:56Z") }}
  • 26,817
  • 2,093,801